【源码粗读】 - Thread源码

182 阅读5分钟

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

Thread

线程

线程初始化方法

类型代码附加
ThreadextendsThread Override run use instance.start()Thread是具体的类
Runableimplements Runnable Override run use new Thread(instance).start()
callableimplements Runnable Override call 丢到线程池里面去Callable不同的地方在于,可以带一个返回值

三者关系

Thread:继承Runnable接口

Runnable:需要注意的是,在1.8中Runnable已经被标注为FunctionalInterface了。

具体初始化关系

Thread:重写run

Runable:重写run

callable:重写call

线程的状态

  • 线程优先级:

    1<= x = 5 <=10

    • 1为最低,10为最大,5为默认
  • 线程状态

    在Thread中,定义了(since 1.5)对应的枚举,一共包括:

    • new

    • RUNNABLE

    • BLOCKED

    • WATIING

    • TIME-WAITING

    • TERMINATED

      6个状态,相关图示如下:

cxZyAx.png

可以看出来,BLOCKED和WAITING本质上都是等待某些临界区资源的状态,只不过:

  • BLOCKED是通过Synchronized进行JVM层面上的显式资源上锁
  • WAITING是线程本身将资源让出时的底层(被JVM屏蔽的底层操作系统层面的)资源上锁,可能是CPU的执行核心等。

相关方法

Object
  • wait

    • 源码描述

      当前线程拥有调用wait方法的Monitor时方可调用。

      线程将主动释放拥有的monitor,直到:

      1.其他线程调用notify/notifyAll时,

      2.其他线程interrupt时

      3.输入的等待时间耗尽时,

      线程才会在重新获取monitor的拥有权之后,重新开始执行。

      如果当前线程在wait前或wait后被其他线程interrupt了,会在对象的锁状态被存储后,抛出interruptedException。

      源码备注中给出的伪代码案例:

      synchronized (obj) {
          while (<condition does not hold>)
              obj.wait();
          ... // Perform action appropriate to condition
      }
      
    • 事实上,实践中大部分的代码都使用了非循环来进行wait,原因也比较简单:while里面的condition是需要额外成本的,不如直接wait。

    • wait会虚假苏醒(spurious wakeup,即:不在上面的三种情况中),这种情况是应用程序需要自己保证不会发生的。

  • notify

    • 源码描述

      • 方法相关

        唤醒一个等待此对象monitor的线程,如果有多个,随机唤醒一个(The choice is arbitrary and occurs at the discretion of the implementation)。

        唤醒的线程在本线程放弃lock之后才会继续执行,并且唤醒的线程将正常去竞争对象的同步权,并不会有优势或劣势。

      • 如何获取monitor

        1. 执行对象的synchronized的实例方法。
        2. 执行对象中的synchronized代码块。
        3. 执行synchronized的静态方法,此时锁的是class。
    • 补充:直接把synchronized(对象)也是一种方法,只是没有上述的那么优雅

  • notifyAll

    • 注释里只说了会全部唤醒,并没有说明顺序
Thread
  • join

    • 看代码实际上就是wait了对应的时间。
  • sleep

    • 源码描述

      等待指定时间,且没有失去当前对monitor的拥有权。

  • yield

    • 源码描述

      暗示(hint)调度器当前线程愿意让出当前处理器的使用权,调度器可以忽视这个暗示。

      Yield是一种启发式的尝试,旨在提高线程之间的相对进度,否则会过度使用CPU。需要使用详细的分析和测试(benchmark)来确保确实能达到这样的效果。

      不推荐使用这个方法。在以下场合是可能会起作用的:

      1. 在debug和测试的时候使用,可以帮助一些由于竟态条件(race conditions)引起的bug。
      2. 在设计一些同步控制构造的时候,例如JUC中的locks。
LockSupport

(todo)进一步的源码解析:juejin.cn/post/684490…

  • park

  • parkNanos

  • parkUntil

    • 这三者的核心代码都是如下这四句。blocked是代表当前线程parking的同步对象。

    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(true, deadline/0L/nanos);
    setBlocker(t, null);
    
  • 源码描述

    使当前线程的线程调度无效,直到permit是存在的。

    如果permit存在,那么会消费且马上返回;不存在的话,当前线程会对线程调度失效,且停止活动,直到以下的事情发生:

    • 其他线程调用unpark方法指定当前线程为对象
    • 其他线程interrupt当前线程
    • 调用错误返回(that is, for no reason

    这个方法并不会返回导致这个方法return的原因。调用者需要重新确认第一次导致线程park的原因。调用者可以通过线程的interrupt的状态来确定。

wait和park的区别

通过代码的描述,可以看出wait和park是近似的:、

  • wait通过notify进行唤醒
  • park通过unpark进行唤醒

并且都是主动让出当前的资源调度。

区别如下:(see:juejin.cn/post/684490…

(1)Object.wait()方法需要在synchronized块中执行;

(2)LockSupport.park()可以在任意地方执行;

(3)Object.wait()方法声明抛出了中断异常,调用者需要捕获或者再抛出;

(4)LockSupport.park()不需要捕获中断异常;

(5)Object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;

(6)LockSupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;

(7)如果在wait()之前执行了notify()会怎样?抛出IllegalMonitorStateException异常;(这一点是不对的,notify的提前调用并不会导致该异常)

(8)如果在park()之前执行了unpark()会怎样?线程不会被阻塞,直接跳过park(),继续执行后续内容;

see : juejin.cn/post/684490…

wait方法通过对象的互斥锁实现线程同步,park是通过一个二元信号量实现线程同步的

wait方法会让线程放弃当前持有的锁,park方法则不会

在wait方法调用前notify是无效的,但可以通过提前调用unpark方法,来让接下来调用park方法的线程提前获取许可(permit值)

\