美文网首页死磕源码
死磕源码系列 - 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