从源码的角度再学「Thread」


  • 管理员

    原创: zhangshaolin 张少林同学
    微信号: zhangshaolin_tonxue

    功能介绍 分享

    前言

    Java中的线程是使用Thread类实现的,Thread在初学Java的时候就学过了,也在实践中用过,不过一直没从源码的角度去看过它的实现,今天从源码的角度出发,再次学习Java Thread,愿此后对Thread的实践更加得心应手。

    从注释开始

    相信阅读过JDK源码的同学都能感受到JDK源码中有非常详尽的注释,阅读某个类的源码应当先看看注释对它的介绍,注释原文就不贴了,以下是我对它的总结:

    • Thread是程序中执行的线程,Java虚拟机允许应用程序同时允许多个执行线程

    • 每个线程都有优先级的概念,具有较高优先级的线程优先于优先级较低的线程执行

    • 每个线程都可以被设置为守护线程

    • 当在某个线程中运行的代码创建一个新的Thread对象时,新的线程优先级跟创建线程一致

    • Java虚拟机启动的时候都会启动一个叫做main的线程,它没有守护线程,main线程会继续执行,直到以下情况发送

    • Runtime 类的退出方法exit被调用并且安全管理器允许进行退出操作

    • 所有非守护线程均已死亡,或者run方法执行结束正常返回结果,或者run方法抛出异常

    • 创建线程第一种方式:继承Thread类,重写run方法

       1  //定义线程类
       2  class PrimeThread extends Thread {
       3        long minPrime;
       4        PrimeThread(long minPrime) {
       5            this.minPrime = minPrime;
       6        }
       7        public void run() {
       8            // compute primes larger than minPrime
       9             . . .
      10        }
      11    }
      12  //启动线程
      13  PrimeThread p = new PrimeThread(143);
      14  p.start();
      
    • 创建线程第二种方式:实现Runnable接口,重写run方法,因为Java的单继承限制,通常使用这种方式创建线程更加灵活

       1  //定义线程
       2   class PrimeRun implements Runnable {
       3        long minPrime;
       4        PrimeRun(long minPrime) {
       5            this.minPrime = minPrime;
       6        }
       7        public void run() {
       8            // compute primes larger than minPrime
       9             . . .
      10        }
      11    }
      12  //启动线程
      13  PrimeRun p = new PrimeRun(143);
      14  new Thread(p).start();
      
    • 创建线程时可以给线程指定名字,如果没有指定,会自动为它生成名字

    • 除非另有说明,否则将null参数传递给Thread类中的构造函数或方法将导致抛出 NullPointerException

    Thread 常用属性

    阅读一个Java类,先从它拥有哪些属性入手:

    	 1  //线程名称,创建线程时可以指定线程的名称
    	 2  private volatile String name;
    	 3  
    	 4  //线程优先级,可以设置线程的优先级
    	 5  private int priority;
    	 6  
    	 7  //可以配置线程是否为守护线程,默认为false
    	 8  private boolean daemon = false;
    	 9  
    	10  //最终执行线程任务的`Runnable`
    	11  private Runnable target;
    	12  
    	13  //描述线程组的类
    	14  private ThreadGroup group;
    	15  
    	16  //此线程的上下文ClassLoader
    	17  private ClassLoader contextClassLoader;
    	18  
    	19  //所有初始化线程的数目,用于自动编号匿名线程,当没有指定线程名称时,会自动为其编号
    	20  private static int threadInitNumber;
    	21  
    	22  //此线程请求的堆栈大小,如果创建者没有指定堆栈大小,则为0。, 虚拟机可以用这个数字做任何喜欢的事情。, 一些虚拟机会忽略它。
    	23  private long stackSize;
    	24  
    	25  //线程id
    	26  private long tid;
    	27  
    	28  //用于生成线程ID
    	29  private static long threadSeqNumber;
    	30  
    	31  //线程状态
    	32  private volatile int threadStatus = 0;
    	33  
    	34  //线程可以拥有的最低优先级
    	35  public final static int MIN_PRIORITY = 1;
    	36  
    	37  //分配给线程的默认优先级。
    	38  public final static int NORM_PRIORITY = 5;
    	39  
    	40  //线程可以拥有的最大优先级
    	41  public final static int MAX_PRIORITY = 10;
    

    Thread 构造方法

    了解了属性之后,看看Thread实例是怎么构造的?先预览下它大致有多少个构造方法:

    a77dcb75-2e68-4785-81f8-a4b242b67382-image.png

    查看每个构造方法内部源码,发现均调用的是名为init的私有方法,再看init方法有两个重载,而其核心方法如下:

     1   /**
     2     * Initializes a Thread.
     3     *
     4     * @param g                   线程组
     5     * @param target              最终执行任务的 `run()` 方法的对象
     6     * @param name                新线程的名称
     7     * @param stackSize           新线程所需的堆栈大小,或者 0 表示要忽略此参数
     8     * @param acc                 要继承的AccessControlContext,如果为null,则为 AccessController.getContext()
     9     * @param inheritThreadLocals 如果为 true,从构造线程继承可继承的线程局部的初始值
    10     */
    11    private void init(ThreadGroup g, Runnable target, String name,
    12                      long stackSize, AccessControlContext acc,
    13                      boolean inheritThreadLocals) {
    14        //线程名称为空,直接抛出空指针异常
    15        if (name == null) {
    16            throw new NullPointerException("name cannot be null");
    17        }
    18        //初始化当前线程对象的线程名称
    19        this.name = name;
    20        //获取当前正在执行的线程为父线程
    21        Thread parent = currentThread();
    22        //获取系统安全管理器
    23        SecurityManager security = System.getSecurityManager();
    24        //如果线程组为空
    25        if (g == null) {
    26            //如果安全管理器不为空
    27            if (security != null) {
    28                //获取SecurityManager中的线程组
    29                g = security.getThreadGroup();
    30            }
    31            //如果获取的线程组还是为空
    32            if (g == null) {
    33                //则使用父线程的线程组
    34                g = parent.getThreadGroup();
    35            }
    36        }
    37
    38        //检查安全权限
    39        g.checkAccess();
    40
    41        //使用安全管理器检查是否有权限
    42        if (security != null) {
    43            if (isCCLOverridden(getClass())) {
    44                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
    45            }
    46        }
    47
    48        //线程组中标记未启动的线程数+1,这里方法是同步的,防止出现线程安全问题
    49        g.addUnstarted();
    50
    51        //初始化当前线程对象的线程组
    52        this.group = g;
    53        //初始化当前线程对象的是否守护线程属性,注意到这里初始化时跟父线程一致
    54        this.daemon = parent.isDaemon();
    55        //初始化当前线程对象的线程优先级属性,注意到这里初始化时跟父线程一致
    56        this.priority = parent.getPriority();
    57        //这里初始化类加载器
    58        if (security == null || isCCLOverridden(parent.getClass()))
    59            this.contextClassLoader = parent.getContextClassLoader();
    60        else
    61            this.contextClassLoader = parent.contextClassLoader;
    62        this.inheritedAccessControlContext =
    63                acc != null ? acc : AccessController.getContext();
    64        //初始化当前线程对象的最终执行任务对象
    65        this.target = target;
    66        //这里再对线程的优先级字段进行处理
    67        setPriority(priority);
    68        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    69            this.inheritableThreadLocals =
    70                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    71        //初始化当前线程对象的堆栈大小
    72        this.stackSize = stackSize;
    73
    74        //初始化当前线程对象的线程ID,该方法是同步的,内部实际上是threadSeqNumber++
    75        tid = nextThreadID();
    76    }
    

    另一个重载init私有方法如下,实际上内部调用的是上述init方法:

    1   private void init(ThreadGroup g, Runnable target, String name,
    2                      long stackSize) {
    3        init(g, target, name, stackSize, null, true);
    4   }
    

    接下来看看所有构造方法:

    1. 空构造方法

      1    public Thread() {
      2        init(null, null, "Thread-" + nextThreadNum(), 0);
      3    }
      

      内部调用的是init第二个重载方法,参数基本都是默认值,线程名称写死为"Thread-" + nextThreadNum()格式,nextThreadNum()为一个同步方法,内部维护一个静态属性表示线程的初始化数量+1:

      1   private static int threadInitNumber;
      2    private static synchronized int nextThreadNum() {
      3        return threadInitNumber++;
      4    }
      

      与第一个构造方法区别在于可以自定义Runnable对象

    2. 自定义执行任务Runnable对象的构造方法

       1    private static int threadInitNumber;
       2    private static synchronized int nextThreadNum() {
       3        return threadInitNumber++;
       4    }
      
    3. 自定义执行任务Runnable对象和AccessControlContext对象的构造方法

      1 Thread(Runnable target, AccessControlContext acc) {
      2    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
      3 }
      
    4. 自定义线程组ThreadGroup和执行任务Runnable对象的构造方法

      1  public Thread(ThreadGroup group, Runnable target) {
      2    init(group, target, "Thread-" + nextThreadNum(), 0);
      3  }
      
    5. 自定义线程名称name的构造方法

      1  public Thread(String name) {
      2    init(null, null, name, 0);
      3  }
      
    6. 自定义线程组ThreadGroup和线程名称name的构造方法

      1  public Thread(String name) {
      2     init(null, null, name, 0);
      3  }
      
    7. 自定义执行任务Runnable对象和线程名称name的构造方法

      1  public Thread(Runnable target, String name) {
      2     init(null, target, name, 0);
      3  }
      
    8. 自定义线程组ThreadGroup和线程名称name和执行任务Runnable对象的构造方法

      1  public Thread(ThreadGroup group, Runnable target, String name) {
      2     init(group, target, name, 0);
      3  }
      
    9. 全部属性都是自定义的构造方法

      1  public Thread(ThreadGroup group, Runnable target, String name,
      2                long stackSize) {
      3     init(group, target, name, stackSize);
      4  }
      

    Thread提供了非常灵活的重载构造方法,方便开发者自定义各种参数的Thread对象。

    常用方法

    这里记录一些比较常见的方法吧,对于Thread中存在的一些本地方法,我们暂且不用管它~

    设置线程名称

    设置线程名称,该方法为同步方法,为了防止出现线程安全问题,可以手动调用Thread的实例方法设置名称,也可以在构造Thread时在构造方法中传入线程名称,我们通常都是在构造参数时设置

    	 1   public final synchronized void setName(String name) {
    	 2         //检查安全权限
    	 3          checkAccess();
    	 4         //如果形参为空,抛出空指针异常
    	 5          if (name == null) {
    	 6              throw new NullPointerException("name cannot be null");
    	 7          }
    	 8        //给当前线程对象设置名称
    	 9          this.name = name;
    	10          if (threadStatus != 0) {
    	11              setNativeName(name);
    	12          }
    	13      }
    

    获取线程名称

    内部直接返回当前线程对象的名称属性

    	1  public final String getName() {
    	2        return name;
    	3    }
    

    启动线程

    	 1  public synchronized void start() {
    	 2      //如果不是刚创建的线程,抛出异常
    	 3      if (threadStatus != 0)
    	 4          throw new IllegalThreadStateException();
    	 5
    	 6      //通知线程组,当前线程即将启动,线程组当前启动线程数+1,未启动线程数-1
    	 7      group.add(this);
    	 8
    	 9      //启动标识
    	10      boolean started = false;
    	11      try {
    	12          //直接调用本地方法启动线程
    	13          start0();
    	14          //设置启动标识为启动成功
    	15          started = true;
    	16      } finally {
    	17          try {
    	18              //如果启动呢失败
    	19              if (!started) {
    	20                  //线程组内部移除当前启动的线程数量-1,同时启动失败的线程数量+1
    	21                  group.threadStartFailed(this);
    	22              }
    	23          } catch (Throwable ignore) {
    	24              /* do nothing. If start0 threw a Throwable then
    	25                it will be passed up the call stack */
    	26          }
    	27      }
    	28  }
    

    我们正常的启动线程都是调用Threadstart()方法,然后Java虚拟机内部会去调用Thredrun方法,可以看到Thread类也是实现Runnable接口,重写了run方法的:

    	1 @Override
    	2 public void run() {
    	3     //当前执行任务的Runnable对象不为空,则调用其run方法
    	4     if (target != null) {
    	5         target.run();
    	6     }
    	7 }
    

    Thread的两种使用方式:

    • 继承Thread类,重写run方法,那么此时是直接执行run方法的逻辑,不会使用target.run();

    • 实现Runnable接口,重写run方法,因为Java的单继承限制,通常使用这种方式创建线程更加灵活,这里真正的执行逻辑就会交给自定义Runnable去实现

    设置守护线程

    本质操作是设置daemon属性

    	 1  public final void setDaemon(boolean on) {
    	 2      //检查是否有安全权限
    	 3      checkAccess();
    	 4      //本地方法,测试此线程是否存活。, 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态
    	 5      if (isAlive()) {
    	 6          //如果线程先启动后再设置守护线程,将抛出异常
    	 7          throw new IllegalThreadStateException();
    	 8      }
    	 9      //设置当前守护线程属性
    	10      daemon = on;
    	11  }
    

    判断线程是否为守护线程

    	1 public final boolean isDaemon() {
    	2     //直接返回当前对象的守护线程属性
    	3     return daemon;
    	4 }
    

    线程状态

    先来个线程状态图:

    2d75533a-f1aa-4078-8a36-dd3fbe757f01-image.png

    获取线程状态:

    	1 public State getState() {
    	2     //由虚拟机实现,获取当前线程的状态
    	3     return sun.misc.VM.toThreadState(threadStatus);
    	4 }
    

    线程状态主要由内部枚举类State组成:

    	 1  public enum State {
    	 2
    	 3     NEW,
    	 4
    	 5
    	 6     RUNNABLE,
    	 7
    	 8
    	 9     BLOCKED,
    	10
    	11
    	12     WAITING,
    	13
    	14
    	15     TIMED_WAITING,
    	16
    	17
    	18     TERMINATED;
    	19 }
    
    • NEW:刚刚创建,尚未启动的线程处于此状态

    • RUNNABLE:在Java虚拟机中执行的线程处于此状态

    • BLOCKED:被阻塞等待监视器锁的线程处于此状态,比如线程在执行过程中遇到synchronized同步块,就会进入此状态,此时线程暂停执行,直到获得请求的锁

    • WAITING:无限期等待另一个线程执行特定操作的线程处于此状态

    • 通过 wait() 方法等待的线程在等待 notify() 方法

    • 通过 join() 方法等待的线程则会等待目标线程的终止

    • TIMED_WAITING:正在等待另一个线程执行动作,直到指定等待时间的线程处于此状态

    • 通过 wait() 方法,携带超时时间,等待的线程在等待 notify() 方法

    • 通过 join() 方法,携带超时时间,等待的线程则会等待目标线程的终止

    • TERMINATED:已退出的线程处于此状态,此时线程无法再回到 RUNNABLE 状态

    线程休眠

    这是一个静态的本地方法,使当前执行的线程休眠暂停执行 millis 毫秒,当休眠被中断时会抛出InterruptedException中断异常

    	 1 /**
    	 2  * Causes the currently executing thread to sleep (temporarily cease
    	 3  * execution) for the specified number of milliseconds, subject to
    	 4  * the precision and accuracy of system timers and schedulers. The thread
    	 5  * does not lose ownership of any monitors.
    	 6  *
    	 7  * @param  millis
    	 8  *         the length of time to sleep in milliseconds
    	 9  *
    	10  * @throws  IllegalArgumentException
    	11  *          if the value of {@code millis} is negative
    	12  *
    	13  * @throws  InterruptedException
    	14  *          if any thread has interrupted the current thread. The
    	15  *          <i>interrupted status</i> of the current thread is
    	16  *          cleared when this exception is thrown.
    	17  */
    	18 public static native void sleep(long millis) throws InterruptedException;
    

    检查线程是否存活

    本地方法,测试此线程是否存活。 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态。

    	1  /**
    	2   * Tests if this thread is alive. A thread is alive if it has
    	3   * been started and has not yet died.
    	4   *
    	5   * @return  <code>true</code> if this thread is alive;
    	6   *          <code>false</code> otherwise.
    	7   */
    	8  public final native boolean isAlive();
    

    线程优先级

    • 设置线程优先级

       1  /**
       2   * Changes the priority of this thread.
       3   * <p>
       4   * First the <code>checkAccess</code> method of this thread is called
       5   * with no arguments. This may result in throwing a
       6   * <code>SecurityException</code>.
       7   * <p>
       8   * Otherwise, the priority of this thread is set to the smaller of
       9   * the specified <code>newPriority</code> and the maximum permitted
      10   * priority of the thread's thread group.
      11   *
      12   * @param newPriority priority to set this thread to
      13   * @exception  IllegalArgumentException  If the priority is not in the
      14   *               range <code>MIN_PRIORITY</code> to
      15   *               <code>MAX_PRIORITY</code>.
      16   * @exception  SecurityException  if the current thread cannot modify
      17   *               this thread.
      18   * @see        #getPriority
      19   * @see        #checkAccess()
      20   * @see        #getThreadGroup()
      21   * @see        #MAX_PRIORITY
      22   * @see        #MIN_PRIORITY
      23   * @see        ThreadGroup#getMaxPriority()
      24   */
      25  public final void setPriority(int newPriority) {
      26      //线程组
      27      ThreadGroup g;
      28      //检查安全权限
      29      checkAccess();
      30      //检查优先级形参范围
      31      if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
      32          throw new IllegalArgumentException();
      33      }
      34      if((g = getThreadGroup()) != null) {
      35          //如果优先级形参大于线程组最大线程最大优先级
      36          if (newPriority > g.getMaxPriority()) {
      37              //则使用线程组的优先级数据
      38              newPriority = g.getMaxPriority();
      39          }
      40          //调用本地设置线程优先级方法
      41          setPriority0(priority = newPriority);
      42      }
      43  }
      

    线程中断

    有一个stop()实例方法可以强制终止线程,不过这个方法因为太过于暴力,已经被标记为过时方法,不建议程序员再使用,因为强制终止线程会导致数据不一致的问题。

    这里关于线程中断的方法涉及三个:

    	1 //实例方法,通知线程中断,设置标志位
    	2 public void interrupt(){}
    	3 //静态方法,检查当前线程的中断状态,同时会清除当前线程的中断标志位状态
    	4 public static boolean interrupted(){}
    	5 //实例方法,检查当前线程是否被中断,其实是检查中断标志位
    	6 public boolean isInterrupted(){}
    

    interrupt() 方法解析

    	 1  /**
    	 2   * Interrupts this thread.
    	 3   *
    	 4   * <p> Unless the current thread is interrupting itself, which is
    	 5   * always permitted, the {@link #checkAccess() checkAccess} method
    	 6   * of this thread is invoked, which may cause a {@link
    	 7   * SecurityException} to be thrown.
    	 8   *
    	 9   * <p> If this thread is blocked in an invocation of the {@link
    	10   * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
    	11   * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
    	12   * class, or of the {@link #join()}, {@link #join(long)}, {@link
    	13   * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
    	14   * methods of this class, then its interrupt status will be cleared and it
    	15   * will receive an {@link InterruptedException}.
    	16   *
    	17   * <p> If this thread is blocked in an I/O operation upon an {@link
    	18   * java.nio.channels.InterruptibleChannel InterruptibleChannel}
    	19   * then the channel will be closed, the thread's interrupt
    	20   * status will be set, and the thread will receive a {@link
    	21   * java.nio.channels.ClosedByInterruptException}.
    	22   *
    	23   * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
    	24   * then the thread's interrupt status will be set and it will return
    	25   * immediately from the selection operation, possibly with a non-zero
    	26   * value, just as if the selector's {@link
    	27   * java.nio.channels.Selector#wakeup wakeup} method were invoked.
    	28   *
    	29   * <p> If none of the previous conditions hold then this thread's interrupt
    	30   * status will be set. </p>
    	31   *
    	32   * <p> Interrupting a thread that is not alive need not have any effect.
    	33   *
    	34   * @throws  SecurityException
    	35   *          if the current thread cannot modify this thread
    	36   *
    	37   * @revised 6.0
    	38   * @spec JSR-51
    	39   */
    	40  public void interrupt() {
    	41      //检查是否是自身调用
    	42      if (this != Thread.currentThread())
    	43          //检查安全权限,这可能导致抛出{@link * SecurityException}。
    	44          checkAccess();
    	45
    	46      //同步代码块
    	47      synchronized (blockerLock) {
    	48          Interruptible b = blocker;
    	49          //检查是否是阻塞线程调用
    	50          if (b != null) {
    	51              //设置线程中断标志位
    	52              interrupt0(); 
    	53              //此时抛出异常,将中断标志位设置为false,此时我们正常会捕获该异常,重新设置中断标志位
    	54              b.interrupt(this);
    	55              return;
    	56          }
    	57      }
    	58      //如无意外,则正常设置中断标志位
    	59      interrupt0();
    	60  }
    
    • 线程中断方法不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦~

    • 只能由自身调用,否则可能会抛出 SecurityException

    • 调用中断方法是由目标线程自己决定是否中断,而如果同时调用了wait,join,sleep等方法,会使当前线程进入阻塞状态,此时有可能发生InterruptedException异常

    • 被阻塞的线程再调用中断方法是不合理的

    • 中断不活动的线程不会产生任何影响

    检查线程是否被中断:

    	 1  /**
    	 2   * Tests whether this thread has been interrupted.  The <i>interrupted
    	 3   * status</i> of the thread is unaffected by this method.
    	 4
    	 5   测试此线程是否已被中断。, 线程的<i>中断*状态</ i>不受此方法的影响。
    	 6   *
    	 7   * <p>A thread interruption ignored because a thread was not alive
    	 8   * at the time of the interrupt will be reflected by this method
    	 9   * returning false.
    	10   *
    	11   * @return  <code>true</code> if this thread has been interrupted;
    	12   *          <code>false</code> otherwise.
    	13   * @see     #interrupted()
    	14   * @revised 6.0
    	15   */
    	16  public boolean isInterrupted() {
    	17      return isInterrupted(false);
    	18  }
    

    静态方法,会清空当前线程的中断标志位:

    	 1   /**
    	 2     *测试当前线程是否已被中断。, 此方法清除线程的* <i>中断状态</ i>。, 换句话说,如果要连续两次调用此方法,则* second调用将返回false(除非当前线程再次被中断,在第一次调用已清除其中断的*状态   之后且在第二次调用已检查之前), 它)
    	 3     *
    	 4     * <p>A thread interruption ignored because a thread was not alive
    	 5     * at the time of the interrupt will be reflected by this method
    	 6     * returning false.
    	 7     *
    	 8     * @return  <code>true</code> if the current thread has been interrupted;
    	 9     *          <code>false</code> otherwise.
    	10     * @see #isInterrupted()
    	11     * @revised 6.0
    	12     */
    	13    public static boolean interrupted() {
    	14        return currentThread().isInterrupted(true);
    	15    }
    

    总结

    记录自己阅读Thread类源码的一些思考,不过对于其中用到的很多本地方法只能望而却步,还有一些代码没有看明白,暂且先这样吧,如果有不足之处,请留言告知我,谢谢!后续会在实践中对Thread做出更多总结记录。


Log in to reply