系列文章目录 java
上一篇咱们讨论了线程的建立,本篇咱们来聊一聊线程的状态转换以及经常使用的几个比较重要的方法。面试
本篇依然是经过源码分析来了解这些知识。segmentfault
本文源码基于jdk1.8 。app
阅读完本文,你应当有能力回答如下常见面试题:函数
在Thread类中, 线程状态是经过threadStatus
属性以及State
枚举类实现的:oop
/* Java thread status for tools, * initialized to indicate thread 'not yet started' */ private volatile int threadStatus = 0; 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; } /** * Returns the state of this thread. * This method is designed for use in monitoring of the system state, * not for synchronization control. * * @return this thread's state. * @since 1.5 */ public State getState() { // get current thread state return sun.misc.VM.toThreadState(threadStatus); }
从源码中能够看出, 线程一共有6种状态, 其状态转换关系以下图所示: 源码分析
值得一提的是,从状态的定义中能够看出,RUNNABLE状态包含了咱们一般所说的running
和ready
两种状态。this
源码中currentThread定义以下:spa
/** * Returns a reference to the currently executing thread object. * * @return the currently executing thread. */ public static native Thread currentThread();
可见,它是一个静态方法,而且是一个native方法,返回的是当前正在执行的线程。线程
爱思考的同窗可能就要问了,如今咱都多核CPU了,同一时刻能够有多个线程跑在不一样的CPU核心上,那当前正在执行的线程有多个,到底返回的是哪个呢?
其实,这里"当前正在执行的线程"指的是当前正在执行这段代码的线程。
咱们知道,线程是CPU调度的最小单位,任意一段代码总得由一个线程执行,因此该方法返回的是正在执行Thread.currentThread
这行代码的线程,例如:
public class ThreadTest { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); } }
输出:
main
咱们知道当一个Java程序启动之后,有一个线程就会立马跑起来,这就是一般所说的Main线程,main线程将会执行java的入口方法main方法,因此当前正在执行Thread.currentThread()方法的线程就是main线程。
谈起sleep方法, 被问的最多的两个问题就是:
这些问题的答案, 你在源码里都能找获得。咱们直接来看源码:
/** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds, subject to * the precision and accuracy of system timers and schedulers. The thread * does not lose ownership of any monitors. * * @param millis * the length of time to sleep in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * if any thread has interrupted the current thread. The * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public static native void sleep(long millis) throws InterruptedException;
可见, sleep方法也是一个静态方法, 而且是native方法, 从注释Causes the currently executing thread to sleep
中能够看出, 它做用于当前正在执行的线程
, 因此上面那个问题咱们就能回答了:
Thread.sleep() 与 Thread.currentThread().sleep() 没有区别
若是硬要说他们有什么区别的话, 那就是一个是用类直接调用静态方法, 一个是用类的实例调用静态方法.
另外, 上面的注释中还有一句很是重要的话:
The thread does not lose ownership of any monitors.
也就是说, 虽然sleep函数使当前线程让出了CPU, 可是, 当前线程仍然持有它所得到的监视器锁, 这与同时让出CPU资源和监视器锁资源的wait方法是不同的。
sleep方法还有另一个版本:
/** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds plus the specified * number of nanoseconds, subject to the precision and accuracy of system * timers and schedulers. The thread does not lose ownership of any * monitors. * * @param millis * the length of time to sleep in milliseconds * * @param nanos * {@code 0-999999} additional nanoseconds to sleep * * @throws IllegalArgumentException * if the value of {@code millis} is negative, or the value of * {@code nanos} is not in the range {@code 0-999999} * * @throws InterruptedException * if any thread has interrupted the current thread. The * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public static void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException("nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } sleep(millis); }
这个方法多加了纳秒级别的延时参数, 可是咱们看源码就知道, 这个多加的纳秒级别的延时并无什么用, 最终该函数仍是调用了上面的单参数native sleep方法, 延时仍是毫秒级别的, 多出来的参数最可能是让当前毫秒级别的延时增长1毫秒.
还记得咱们上次讲的wait方法吗?咱们来对比下:
public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException("nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); }
怎么样?是否是很像?二者只不过在从纳秒向毫秒的进位处有细微的差异,我猜这个不统一是历史缘由致使的。
另外,值得一提的是,wait有无参的wait()
方法,它调用的是wait(0)
,表示无限期等待,而sleep并无无参数的版本,那么sleep(0)
表明什么呢?
这一点在源码里面并无说起,可是经过猜想sleep方法的定义咱们知道,它是让出CPU 0毫秒,这听上去好像没有什么意义,但其实调用Thread.sleep(0)的当前线程确实被“冻结”了一下,让其余线程有机会优先执行。也就是说当前线程会释放一些未用完的时间片给其余线程或进程使用,就至关于一个让位动做,这看上去就和下面要说的yield方法很像了。
既然上面谈到了sleep(0)方法, 就不得不提yield方法了:
/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();
yield方法也是一个native方法, 从它的注释能够看出A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.
它对于CPU只是一个建议, 告诉CPU, 当前线程愿意让出CPU给其余线程使用, 至于CPU采不采纳, 取决于不一样厂商的行为, 有可能一个线程刚yield出CPU, 而后又立马得到了CPU。与之相对, sleep方法必定会让出CPU资源, 而且休眠指定的时间, 不参与CPU的竞争.
因此调用yield方法不会使线程退出RUNNANLE
状态,顶多会使线程从running 变成 ready,
可是sleep方法是有可能将线程状态转换成TIMED_WAITING
的。
isAlive
方法用于检查线程是否还活着,它是一个native方法,但不是静态方法,也就是说它必须被线程的实例所调用。
其实你们能够思考一下它为何不是静态方法,由于静态方法通常都是做用于当前正在执行的线程
,既然是“当前正在执行”,那必然是Alive
的,因此做为静态方法调用并无意义。
/** * Tests if this thread is alive. A thread is alive if it has * been started and has not yet died. * * @return <code>true</code> if this thread is alive; * <code>false</code> otherwise. */ public final native boolean isAlive();
join方法是另外一个能将线程状态转换成WAITING
或者TIMED_WAITING
的,它和wait方法同样,有三个版本,咱们一个个来看:
/** * Waits at most {@code millis} milliseconds for this thread to * die. A timeout of {@code 0} means to wait forever. * * <p> This implementation uses a loop of {@code this.wait} calls * conditioned on {@code this.isAlive}. As a thread terminates the * {@code this.notifyAll} method is invoked. It is recommended that * applications not use {@code wait}, {@code notify}, or * {@code notifyAll} on {@code Thread} instances. * * @param millis * the time to wait in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * if any thread has interrupted the current thread. The * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
这段源码注释的开头部分就告诉了咱们join方法的做用:
Waits at most {@code millis} milliseconds for this thread to die. A timeout of {@code 0} means to wait forever.
也就是说,该方法等待this thread
终止,最多等指定的时间,若是指定时间为0,则一直等。
这里有两个问题须要弄清楚:
this thread
终止?this thread
指的是哪一个线程?为了便于说明,咱们直接来看一个例子:
public class JoinMethodTest { private static void printWithThread(String content) { System.out.println("[" + Thread.currentThread().getName() + "线程]: " + content); } public static void main(String[] args) { printWithThread("开始执行main方法"); Thread myThread = new Thread(() -> { printWithThread("我在自定义的线程的run方法里"); printWithThread("我立刻要休息1秒钟, 并让出CPU给别的线程使用."); try { Thread.sleep(1000); printWithThread("已经休息了1秒, 又从新得到了CPU"); printWithThread("我休息好了, 立刻就退出了"); } catch (InterruptedException e) { e.printStackTrace(); } }); try { myThread.start(); printWithThread("我在main方法里面, 我要等下面这个线程执行完了才能继续往下执行."); myThread.join(); printWithThread("我在main方法里面, 立刻就要退出了."); } catch (InterruptedException e) { e.printStackTrace(); } } }
在上面的例子中,咱们在main方法中调用了 myThread.join()
,注意上面这段代码有两个线程,一个是执行main方法的线程,一个是咱们自定义的myThread
线程,因此上面的两个问题的答案是:
this thread
的终止,由于咱们在main方法中调用了myThread.join()
this thread
线程指的是myThread
线程,由于咱们在myThread对象上调用了join方法。上面这段代码的执行结果为:
[main线程]: 开始执行main方法 [main线程]: 我在main方法里面, 我要等下面这个线程执行完了才能继续往下执行. [Thread-0线程]: 我在自定义的线程的run方法里 [Thread-0线程]: 我立刻要休息1秒钟, 并让出CPU给别的线程使用. [Thread-0线程]: 已经休息了1秒, 又从新得到了CPU [Thread-0线程]: 我休息好了, 立刻就退出了 [main线程]: 我在main方法里面, 立刻就要退出了.
从运行结果能够看出,虽然myThread线程(即Thread-0线程)中途让出了CPU, main线程仍是必须等到其执行完毕了才能继续往下执行,咱们如今修改一下代码,让main线程最多等0.5秒,即将myThread.join()
改成myThread.join(500);
,则结果以下:
[main线程]: 开始执行main方法 [main线程]: 我在main方法里面, 我要等下面这个线程执行完了才能继续往下执行. [Thread-0线程]: 我在自定义的线程的run方法里 [Thread-0线程]: 我立刻要休息1秒钟, 并让出CPU给别的线程使用. [main线程]: 我在main方法里面, 立刻就要退出了. [Thread-0线程]: 已经休息了1秒, 又从新得到了CPU [Thread-0线程]: 我休息好了, 立刻就退出了
咱们看到,因为main线程最多等待myThread 0.5秒,在myThread休眠的一秒内,它就不等了,继续往下执行,而随后myThread抢占到CPU资源继续运行。
经过列子有了感性的认识后,咱们再来看源码,首先看join(0)部分:
public final synchronized void join(long millis) throws InterruptedException { ... if (millis == 0) { while (isAlive()) { wait(0); } } else { ... } ... }
这是一个自旋操做,注意,这里的isAlive
和wait(0)
方法都是线程实例的方法,在上面的例子中就是myThread
的方法,Thread虽然是一个线程类,但只是特殊在它的native方法上,除此以外,它就是个普通的java类,而java中全部的类都继承自Object
类,因此Thread类继承了Object的wait方法,myThread
做为线程类的实例,天然也有wait方法。
咱们以前说wait方法的时候提到过,执行wait方法必须拿到监视器锁,而且必须在同步代码块中调用,这里咱们检查join方法发现,它确实被synchronized
关键字修饰,而且是一个非静态方法,因此它使用的是当前对象实例的监视器锁(this)。
好像开始复杂了,咱们从头至尾捋一捋(注意了!敲黑板了!这段比较绕! ):
myThread.join()
,main方法由main线程执行,因此执行myThread.join()
这行代码的“当前线程”是main线程。myThread
线程是否还存活,注意,这里的isAlive是myThread线程的方法,它是检查myThread线程是否还活着,而不是当前线程(当前线程是执行isAlive方法的线程,即main线程)。WAITING
状态。WAITING
状态被唤醒后(经过notify,notifyAll或者是假唤醒), 将继续竞争监视器锁,当成功得到监视器锁后,他将从调用wait的地方恢复,继续运行。因为wait方法在while循环中,则它将继续检查myThread
线程是否存活,若是仍是没有终止,则继续挂起等待。myThread
线程终止运行(或者有中断异常抛出)。有的细心的同窗可能就要问了: 要是没有人调用notify
或者notifyAll
,也没有假唤醒状态的发生,那main线程不就一直被wait(0)
方法挂起了吗?这样以来不就连检测myThread
线程是否存活的机会都没有吗?这样即便myThread
终止了,也没法退出啊。
关于这一点,注释中实际上是作了解释的:
As a thread terminates the {@code this.notifyAll} method is invoked.
咱们知道,wait(0)方法的监视器锁就是myThread对象(this), 而当myThread终止执行时,this.notifyAll会被调用,因此全部等待this锁的线程都会被唤醒,而main线程就是等待在这个监视器锁上的线程,所以myThread运行结束时,main线程会从wait方法处被唤醒。
另外,注释中还多加了一句:
It is recommended that applications not use {@code wait}, {@code notify}, or {@code notifyAll} on {@code Thread} instances.
这个推荐仍是颇有必要的,至于为何,就给你们留做思考题吧<( ̄︶ ̄)>
不过我这里再啰嗦一句,必定要分清执行代码的线程和方法所属的线程类所表明的线程!
例如,在上面的例子中:
myThread.join()
是myThread对象的方法,可是执行这个方法的是main线程;isAlive()
是myThread对象的方法,可是执行这个方法的是main线程,而这个方法检测是myThread线程是否活着wait(0)
是myThread对象的方法,可是执行这个方法的是main线程,它使得main线程挂起,可是main线程是在myThread对象表明的monitor上挂起。这里最重要的是区分“myThread对象”和“myThread线程”,myThread对象有时候表明了myThread线程,例如myThread对象的isAlive
方法,检测的就是它表明的myThread线程是否活着,可是其实大多数时候,myThread对象就是普通的java对象,这个对象的方法一般也都是由其余线程(例如上面例子中的main线程)来执行的,对于咱们自定义的线程来讲(例如上面的myThread线程),一般由它本身执行的方法就只有传进入的run
方法了。
再回到上面的例子,从上面的分析中能够看出,join(0)方法实现了必定程度上的线程同步,即当前线程只有等join方法所属的线程对象所表明的线程终止执行了才能继续往下执行,不然将一直挂起等待。
这一点也说明使用join(0)是很危险的,由于若是myThread
线程由于得不到资源一直被挂起,而main线程又在等待myThread
线程终止,则程序永远会停在那里,没法终止,因此源码中提供了限时等待的版本:
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; ... if (millis == 0) { ... } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
与无限期等待不一样的是,限时等待只等待指定时间,若是指定的时间到了就直接从循环中跳出来,使用的wai方法也是限时wait的版本,定时时间到了以后,main线程会被自动唤醒。上面的代码是自解释的,我就再也不赘述了。
接下来咱们再来看看其余两个版本的join方法:
public final void join() throws InterruptedException { join(0); } public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException("nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); }
可见,其余两个版本最终调用的都是咱们分析的初版本,这和wait方法,sleep方法很像,至于为何wait方法和join方法都提供了无参方法而sleep方法没有,我我的认为是为了保持语义的一致性:
wait()
和join()
分别和wait(0)
和join(0)
等价,他们都表明了无限期等待,而sleep(0)并不表明无限期等待,因此sleep方法没有无参的形式,以防止语义上的混乱。除这点以外,这三个方法在两个参数的版本XXX(long millis, int nanos)中的实现,都大同小异。
另外最后一点值得注意的是,咱们在join
方法中只调用了isAlive
方法检测线程是否存活,并无启动这个线程,也就是说,若是咱们想要实现当前线程等待myThread
线程执行完成以后再执行的效果,就必须在调用myThread.join()
以前调用myThread.start()
让线程先跑起来,不然join
方法发现isAlive
为false会当即退出,myThread
线程就不会被执行,你们能够将myThread.start()
注释掉本身跑一跑试试看。
(完)
查看更多系列文章:系列文章目录