一、多线程基本概念及基本函数介绍

进程和线程

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

线程和进程两个名词不过是对应的CPU时间段的描述
进程就是包换上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文
CPU在执行的时候没有进行上下文切换的。---网上这句化有问题

  1. 什么是 CPU 上下文?

    CPU 寄存器是 CPU 内置的容量小、但速度极快的内存。
    程序计数器则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。

  2. 什么是 CPU 上下文切换?

    就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
    而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。
    根据任务的不同,可以分为以下三种类型 - 进程上下文切换 - 线程上下文切换 - 中断上下文切换

系统调用

从用户态到内核态的转变,需要通过系统调用来完成。比如,当我们查看文件内容时,就需要多次系统调用来完成:首先调用 open() 打开文件,然后调用 read() 读取文件内容,并调用 write() 将内容写到标准输出,最后再调用 close() 关闭文件。

在这个过程中就发生了 CPU 上下文切换,整个过程是这样的:

  1. 保存 CPU 寄存器里原来用户态的指令位
  2. 为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。
  3. 跳转到内核态运行内核任务。
  4. 当系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。

所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。(用户态-内核态-用户态)
不过,需要注意的是,系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。系统调用过程中一直是同一个进程在运行

进程上下文切换

进程是由内核来管理和调度的,进程的切换只能发生在内核态。
所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。
因此,进程的上下文切换就比系统调用时多了一步:在保存内核态资源(当前进程的内核状态和 CPU 寄存器)之前,需要先把该进程的用户态资源(虚拟内存、栈等)保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。

线程上下文切换

线程与进程最大的区别在于:线程是调度的基本单位,而进程则是资源拥有的基本单位。说白了,所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。

所以,对于线程和进程,我们可以这么理解: - 当进程只有一个线程时,可以认为进程就等于线程。 - 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。当然,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。
在java中,线程是与操作系统的原生线程一一对应的,所以当阻塞一个线程时,需要从用户态切换到内核态执行阻塞操作这是很耗时的。

线程安全

所谓多线程安全问题,简单点说就是多个线程操作一个共享变量导致数据不一致的问题。为什么会产生这个问题?这就要说到java的内存模型。

  1. 内存可见性问题
    java内存模型规定,将所有变量都放到主内存中也就是堆,当线程使用变量时,它会把主内存中的变量复制到自己的工作内存(一般为cpu的一级缓存)。
  2. 原子性操作问题
    在一次操作中要么都执行,要么都不执行。
    i++问题
  3. java指令重排序
    java内存模型允许比阿尼器和处理器对指令重排序以提高运行性能,在单线程下没问题,但在多线程下会存在问题.
  4. 伪共享问题
    为了解决系统中主内存和cpu缓存中的速度差问题,会在CPU和主内存之间体检一级或多级缓存。而Cache内部时按行存储的,这就会产生一个问题:当多个线程同时修改一个缓存行里面的多个变量时,由于同时只能有一个线程操作缓存行,其他只能等待。所以相比将每个变量放到一个缓存行里,性能会下降,这就是伪共享


    可见性问题.PNG

如上图,线程1 和线程2第一次读的时候会命中主内存,但在其他时候会命中线程中的变量副本v1或者v2,一旦发生修改,则可能会产生数据不一致问题。

线程状态

线程状态转移图

线程共包括以下5种状态:

  1. 初始状态,也就是刚创建new,但并没有启动
  2. 就绪状态,执行了start()方法之后,注意,这个时候并没有立即启动,它会在一个合适的时间拿到CPU的执行权,才开始执行
  3. 运行状态,线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
  4. 阻塞状态,图上说的阻塞状态是指已经获取到锁但没有继续运行
  5. run()方法结束

基本函数介绍

主要介绍一下函数:

  1. sleep()、yield()是Thread的静态方法 ,
  2. wait()、notify()、notifyAll() 是Object的方法
  3. join()
方法名 出处 是否阻塞 是否释放锁 作用
wait() Object 当前线程进入等待队列
notify() Object 不涉及 唤醒等待队列中的某一个线程,转为锁池
notifyAll() Object 不涉及 唤醒对象等待队列中的所有线程,转为锁池
sleep() Thread静态方法 休眠,让出cpu
yield() Thread静态方法 让出cpu时间片,重新进入锁池
join() Thread普通方法 等待其他线程执行完毕
interrupt() Thread普通方法 不涉及 不涉及 设置中断标志位true,并不实际中断,如果线程正调用wait()、join()、sleep()方法,那么会跑出InterruptException
isInterrupted() Thread普通方法 不涉及 不涉及 返回中断标志
interrupted() Thread静态方法 不涉及 不涉及 检测中断标志,但如果为true,还会清除中断标志,注意此方法是获取当前线程而不是调用对象的。

举例: wait()方法

  • 要点一:调用wait()方法之前,必须获取该对象的监视器锁
    如下会报错

    Thread t =new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("testWait begin!");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t.start();
    //java.lang.IllegalMonitorStateException
    // 此时调用wait()的对象是this,但我们并没有获取this的对象锁
    

    获取对象监视器锁

      ```
      // 代码块
      synchronized(this|object){
    
      }
      ```
      上述例子加上对象锁,再wait()则不会报错
      输出 testWait begin!
      ```
      // 同步方法
      synchronized void add (){
          //...
      }
      ```
    
  • 要点二:wait() 会被阻塞,直到以下事件发生:

    • 其他线程调用了该线程的interrupt()
      t.start();
      Thread.sleep(200);
      t.interrupt();
      
    • 其他线程调用了该共享对象的notify()或者notifyAll()方法
      注意: 同wait()方法一样,notify和notifyAll调用的前提条件也是获取对象锁。
      下面直接调用会报错:
      Runnable runnable=new Runnable() {
              @Override
              public void run() {
                  System.out.println("testWait begin!");
                  synchronized (this){
                      try {
                          wait();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
                  System.out.println("testWait end!");
              }
          };
      Thread t =new Thread(runnable);
      t.start();
      Thread.sleep(200);
      runnable.notify();
      //Exception in thread "main" java.lang.IllegalMonitorStateException
      
      需要改成:
      t.start();
      Thread.sleep(200);
      synchronized (runnable){
          runnable.notify();
      }
      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351