美文网首页死磕源码
死磕源码系列 - Thread

死磕源码系列 - Thread

作者: sunyelw | 来源:发表于2019-09-28 21:55 被阅读0次

    线程天天用,看过其完整源码的有多少?


    线程目录

    • 构造
    • 启动
    • 状态
    • 中断

    构造

    1. 创建一个线程有几种方式?

    Threads are represented by the Thread class. The only way for a user to create a thread is to create an object of this class; each thread is associated with such an object. A thread will start when the start() method is invoked on the corresponding Thread object.

    Java语言规范中告诉我们只有一种方式:

    • 创建一个Thread类实例
    • 调用Thread#start()方法
    1. Thread类有多少种构造函数?

    八种,实际传入的参数最多只有四个,最终都是调用此方法:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    
    • group 所属线程组
    • target 目标线程
    • name 线程名
    • stackSize 栈深 (线程私有内存空间, 传0忽略此参数)

    继续看下这个方法的最终形态

    /**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     * @param inheritThreadLocals if {@code true}, inherit initial values for
     *            inheritable thread-locals from the constructing thread
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
    
        this.name = name;
    
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */
    
            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }
    
            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
    
        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();
    
        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
    
        g.addUnstarted();
    
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
    
        /* Set thread ID */
        tid = nextThreadID();
    }
    

    不展开了, 看看注释吧~

    启动

    现在一个线程创建好了,怎么启动呢?- start()

    /**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
    
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);
    
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    
    • 首先这是一个同步方法, 表示可能有多个线程想要启动此线程, 但只有一个会成功

    在源码中只要用到了同步块或CAS的, 都表示这块代码有可能被多个线程同时执行

    • 第一段就是校验了, 如果已经被其他线程捷足先登启动好了, 那么再启动就报个错IllegalThreadStateException
    • 添加到线程组 (线程组这块下次说)
    • 调用native方法 start0(), 如果启动失败线程组需要进一步处理

    让我们通过此方法注释中的描述来了解一下到底这个start0()方法做了啥:

    使这个线程开始执行,JVM会执行当前线程的run()方法
    执行start()方法的结果就是:两个线程并发执行:(1)当前线程,也就是执行start()方法的这个线程;(2)另一个线程,执行run()方法的线程.
    多次启动一个线程永远是非法操作, 尤其是一个线程完成执行就不会被重新启动. (my English is poor....)

    再看下Threadrun方法做了啥

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    

    单纯地调用传入的targetrun()方法....

    问两个问题:

    • Thread类的run()方法跟普通方法有啥区别吗?
    • 为啥启动一个新的线程永远是需要调用start()方法?

    其实原因就在于这个nativestart0(), 上面注释中明确说了, 此方法调用后的结果是两个线程, 会另起一个线程去执行run()方法.

    如果你直接调用run()启动会怎么样呢? 很简单, 就是顺序执行呗, 当前线程执行完了run()方法再往下执行, 线程池中就是这种方式来控制线程数量.(参见 死磕源码系列 - ThreadPoolExecutor )

    状态

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,
    
        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,
    
        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,
    
        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,
    
        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,
    
        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    
    状态 描述
    NEW 线程创建好但还没有启动
    RUNNABLE 运行中或者等待调度运行,就是传说中的等待时间片,执行了start()方法之后的状态
    BLOCKED 等待进入一个Monitor锁 (注释中最后半句 调用wait()方法后重新进入同步块, 这里有点问题,后面说)
    WAITING 执行Object#wait()/Thread#join()/LockSupport#park()方法后的状态
    TIMED_WAITING 执行Thread#sleep/Object#wait(long)/Thread#join(long)/LockSupport#parkNanos/LockSupport#parkUntil方法后的状态
    TERMINATED 线程完成执行后的状态

    可能经常弄混淆的就是BLOCKED和两个WAITING状态,下面看一个例子(注意住的是什么):

    1、如果类锁被获取,那么其他类锁的代码将同步阻塞
    2、如果对象锁被获取,那么同一个对象锁的代码将同步阻塞

    public class ThreadSleepWait {
    
        public static void main(String[] args){
            Stream.of("T1", "T2").forEach(name -> new Thread(ThreadSleepWait::show, name).start());
        }
    
        private static void show() {
            synchronized (ThreadSleepWait.class) {
                System.out.println("[" + Thread.currentThread().getName() + "] begin...");
                try {
                    ThreadSleepWait.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("[" + Thread.currentThread().getName() + "] end...");
            }
        }
    }
    

    查看堆栈:

    "T2" #12 prio=5 os_prio=0 tid=0x0000000019b74800 nid=0x1f4c in Object.wait() [0x000000001a43f000]
       java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6718638> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
        at java.lang.Object.wait(Object.java:502)
        at com.example.demo.thread.ThreadSleepWait.show(ThreadSleepWait.java:28)
        - locked <0x00000000d6718638> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
        at com.example.demo.thread.ThreadSleepWait$$Lambda$2/769287236.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
    
    "T1" #11 prio=5 os_prio=0 tid=0x0000000019b73800 nid=0x3e3c in Object.wait() [0x000000001a33f000]
       java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6718638> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
        at java.lang.Object.wait(Object.java:502)
        at com.example.demo.thread.ThreadSleepWait.show(ThreadSleepWait.java:28)
        - locked <0x00000000d6718638> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
        at com.example.demo.thread.ThreadSleepWait$$Lambda$2/769287236.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
    
    
    • 俩线程都处于WAITING状态, 符合上面场景, 另外两个方法可以自行试试~

    输出结果是:

    [T1] begin...
    [T2] begin...
    
    • 两个线程都能执行, 说明wait()释放资源的同时也释放了Monitor

    把代码改动一下:

    public static void main(String[] args){
        Stream.of("T1", "T2").forEach(name -> new Thread(ThreadSleepWait::show, name).start());
    }
    
    private static void show() {
        synchronized (ThreadSleepWait.class) {
            System.out.println("[" + Thread.currentThread().getName() + "] begin...");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("[" + Thread.currentThread().getName() + "] end...");
        }
    }
    

    输出一行后

    [T1] begin...
    

    查看堆栈:

    "T2" #12 prio=5 os_prio=0 tid=0x0000000019beb000 nid=0x4fc8 waiting for monitor entry [0x000000001a48f000]
       java.lang.Thread.State: BLOCKED (on object monitor)
        at com.example.demo.thread.ThreadSleepWait.show(ThreadSleepWait.java:25)
        - waiting to lock <0x00000000d6718718> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
        at com.example.demo.thread.ThreadSleepWait$$Lambda$2/769287236.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
    
    "T1" #11 prio=5 os_prio=0 tid=0x0000000019bea800 nid=0x2184 waiting on condition [0x000000001a38f000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.example.demo.thread.ThreadSleepWait.show(ThreadSleepWait.java:30)
        - locked <0x00000000d6718718> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
        at com.example.demo.thread.ThreadSleepWait$$Lambda$2/769287236.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
    
    • T1得到运行, 调用Thread#sleep(long)方法后处于TIMED_WAITING状态
    • 由于此锁为类锁, T2必须等待T1释放此锁, 也就是处于等待进入一个同步块的状态, 即BLOCKED

    这里顺带提一句sleepwait的区别, 上面的例子也有印证.

    • wait方法会释放锁, 而且会释放此锁上的所有资源, 注意一点, 若此线程同时持有其他锁并不会释放
    • wait方法有wait()方法, 然后wait(0)表示永久等待
    • sleep方法不会释放已持有锁, 但会释放CPU资源
    • sleep方法没有sleep()方法, sleep(0)表示不休眠(表象是这样, 其实会让出一些未使用的时间片)

    wait为啥需要在同步块中执行?因为wait的意思就是释放锁, 如果你都没有进入同步块(获得锁), 你又如何释放呢? 这个时候就会抛出一个锁状态异常IllegalMonitorStateException

    最终输出:

    [T1] begin...
    [T1] end...
    [T2] begin...
    [T2] end...
    
    • 阻塞执行。

    中断

    首先, 中断不等于线程结束.

    没有任何文档中明确指出线程被中断后应该被终结.
    最近看的源码中也有一些中断处理的例子, 比如线程池中的工作线程使用AQS来防止执行任务时被中断(提前锁住), ReentrantLock中的中断延迟响应等等;

    那么中断到底是什么?

    先来看下Thread类中的中断相关方法:

    // 仅返回中断标识
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    
    // 静态方法, 清空中断标识, 同时返回原有中断标识
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    
    // 仅设置中断标识 -> true
    public void interrupt() {
        interrupt0();           // Just to set the interrupt flag
    }
    
    
    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);
    
    1. 处理机制
    • 中断一个阻塞线程(wait/sleep/join)
      抛出 InterruptedException 异常, 并清除中断标识位(置为false)

    Java 高并发编程详解一书中有一句话我觉得解释得很有道理

    可中断方法捕获到了中断信号后,为了不影响线程中其他方法的使用,将中断标识复位是一种很合理的设计。

    • 中断一个运行线程
      仅将中断标识置为true, 不会抛出 InterruptedException 异常

    • 注释中还单独列举I/O operation{@link java.nio.channels.Selector}的处理(比如抛出指定异常)

    1. 应答机制
    • 运行时始终检测中断标识位 while(!t.isInterrupted()) {...}

    • 阻塞时正确处理中断异常 InterruptedException

    这里有个需要注意的点是当抛出InterruptedException时, 其中断标识位会被清除(也就是设回 false), 所以建议是不要捕获了此异常后啥都不做, 推荐做法是直接往上抛出.

    中断标识与 InterruptedException 异常 仅是线程对外提供能被感知到的一种信号, 线程自身决定如何处理此信号

    while(!t.isInterrupted()) {
        try {
            // main work
        } catch (InterruptedException e) {
            // do something when interrupted
        }
    }
    

    十点多啦,回家喝CoCo~~

    相关文章

      网友评论

        本文标题:死磕源码系列 - Thread

        本文链接:https://www.haomeiwen.com/subject/trptpctx.html