zoukankan      html  css  js  c++  java
  • 从源码的角度再学「Thread」

    前言

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

    从注释开始

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

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

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

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

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

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

      • Runtime 类的退出方法exit被调用并且安全管理器允许进行退出操作
      • 所有非守护线程均已死亡,或者run方法执行结束正常返回结果,或者run方法抛出异常
    • 创建线程第一种方式:继承Thread类,重写run方法

       1//定义线程类
      2class 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//启动线程
      13PrimeThread p = new PrimeThread(143);
      14p.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//启动线程
      13PrimeRun p = new PrimeRun(143);
      14new Thread(p).start();
    • 创建线程时可以给线程指定名字,如果没有指定,会自动为它生成名字

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

    Thread 常用属性

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

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

    所有的属性命名都很语义化,其实已看名称基本就猜到它是干嘛的了,难度不大~~

    Thread 构造方法

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

    查看每个构造方法内部源码,发现均调用的是名为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方法:

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

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

    1. 空构造方法

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

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

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

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

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

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

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

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

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

      1 public Thread(ThreadGroup group, String name) {
      2    init(group, 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    }

    启动线程

     1public 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属性

     1public 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    }

    线程状态

    先来个线程状态图:

    获取线程状态:

    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做出更多总结记录。

    最后

    由于篇幅较长,暂且先记录这些吧,后续会不定期更新原创文章,欢迎关注公众号 「张少林同学」!

  • 相关阅读:
    【转】 MySQL高级知识(一)——基础
    inline元素的间距问题
    ES6对于数组的扩展
    JavaScript的垃圾回收机制
    call() apply() bind()
    防抖和节流
    Promise
    js的事件机制
    Javascript异步操作的异常处理
    JavaScript的事件执行机制及异步
  • 原文地址:https://www.cnblogs.com/zhangshaolin/p/10297676.html
Copyright © 2011-2022 走看看