相关Java代码示例使用Java 11
在之前的一篇文章中简单介绍了线程的一些基础概念以及如何创建线程等,这一篇继续前面的旅程,来聊一聊Java线程的生命周期。了解Java线程各个状态之间的转换,将有利于我们理解线程运行的逻辑。
通用线程生命周期
前面说,Java线程其实依赖的是底层OS的线程机制,而现在主流的OS实现的线程都遵循POSIX线程标准,所以我们有必要了解一下通用的线程状态。在此基础上,才能知道Java的线程是如何与之相对应的。
通用的线程状态包括五种:
初始化状态(NEW):编程语言层面线程已创建,但在OS层面线程还未创建,所以只是一个逻辑上的状态,不会分配CPU执行;
可运行状态(RUNNABLE):OS层面线程已创建,等OS分配CPU执行;
运行状态(RUNNING):若有空闲的CPU,OS会从所有可运行状态的线程中,按照线程的等待时间、优先级等条件从中挑选一个,分配CPU执行,此时线程状态就变成了运行状态;
休眠状态(SLEEPING):运行状态的线程如果调用一个阻塞api(如以阻塞方式读文件)、主动休眠或等待某个事件(如条件变量),此时线程会让出CPU使用权,进入休眠状态,休眠状态的线程无法再获取到CPU使用权,除非等待的条件满足,线程会再次进入可运行状态,等待OS调度;
终止状态(TERMINATED):线程执行完成(正常或异常)的状态,代表线程的生命周期完结。
状态机如下:
不同的编程语言实现的线程,都是对通用线程生命周期的细化或简化,下面就看看Java是如何实现的。
Java线程生命周期
线程状态
在java.lang.Thread类中定义了6种状态:
/**
* A thread state. A thread can be in one of the following states:
...
...
* <p>
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
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 {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* 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;
}
其实上面Java-doc就已经把每种状态的含义解释的很清楚了,下面我们结合代码示例将每类状态进行补充。
-
NEW
:初始化状态Thread state for a thread which has not yet started.
线程被创建出来,但还未启动,对应上面通用线程状态中的初始化状态。
下面测试代码打印的日志,可以观察到myThread的状态为NEW,此时myThread并未启动。
@Log4j2 public class MyThread extends Thread { @Override public void run() { log.info("myThread is running..."); } } @Log4j2 public class ThreadCreateTest { @Test public void testThreadState() { MyThread myThread = new MyThread(); // 16:57:02.078 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[Thread-3,5,main] state: NEW displayThreadInfo(myThread); } private void displayThreadInfo(Thread thread){ log.info("{} state: {}", thread, thread.getState()); } }
-
RUNNABLE
:可运行状态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.
JVM中没有细化出一个RUNNING状态,而是将RUNNING状态包含在RUNNABLE状态中。
也就是说,处于RUNNABLE状态的线程可能正在运行,也可能没有运行,而是等待OS调度CPU(线程在就绪队列中排队)。
现在主流的桌面及服务器OS都使用抢占式调度:每个线程被分配一个时间片来执行任务,时间一到,OS就会回收CPU使用权,继续下一次分配。
NEW状态的线程通过调用start()方法,就可以进入RUNNABLE状态,运行示例如下:
@Log4j2 public class ThreadCreateTest { @Test public void testThreadState() { MyThread myThread = new MyThread(); displayThreadInfo(myThread); myThread.start(); // 17:25:11.021 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[Thread-3,5,main] state: RUNNABLE displayThreadInfo(myThread); } }
这里插播一个问题:如果线程调用两次start()方法会有什么结果?
答案是:抛出IllegalThreadStateException,查阅start()方法代码就可以发现,该方法被加了synchronized锁,并且会判断线程状态,防止重复启动。
public class Thread implements Runnable { ... ... 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 */ } } } private native void start0(); ... ... }
-
BLOCKED
:阻塞状态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}.
当前线程运行时,试图通过synchronized去获取某个对象的monitor lock,如果该lock已经被其他线程占有,那么当前线程就从RUNNABLE状态进入BLOCKED状态。进入BLOCKED状态后,线程不再活动,释放CPU使用权,直到获取lock,这时线程重新回到RUNNABLE状态。
我们启动两个线程来模拟一下:
@Log4j2 public class RunnableExample implements Runnable { private static final Object LOCK = new Object(); @SneakyThrows(InterruptedException.class) @Override public void run() { synchronized (LOCK) { log.info("get lock..."); // 休眠3秒,模拟线程等待锁 Thread.sleep(3000); } // 这里也打印一次线程状态,防止线程很快运行结束,测试函数不好捕捉 log.info("{} state: {}", Thread.currentThread(), Thread.currentThread().getState()); } } @Log4j2 public class ThreadCreateTest { @Test public void testThreadState() throws InterruptedException { RunnableExample runnableExample = new RunnableExample(); Thread thread1 = new Thread(runnableExample); Thread thread2 = new Thread(runnableExample); thread1.start(); // 休眠0.5秒,保证thread1可以先获得锁 Thread.sleep(500); thread2.start(); for (int i = 0; i < 7; i++) { displayThreadInfo(thread2); Thread.sleep(1000); } } }
输出日志如下:
11:11:25.071 [Thread-3] INFO com.learn.core.chapter14.RunnableExample - get lock... 11:11:25.572 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[Thread-4,5,main] state: RUNNABLE 11:11:26.574 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[Thread-4,5,main] state: BLOCKED 11:11:27.576 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[Thread-4,5,main] state: BLOCKED 11:11:28.079 [Thread-4] INFO com.learn.core.chapter14.RunnableExample - get lock... 11:11:28.079 [Thread-3] INFO com.learn.core.chapter14.RunnableExample - Thread[Thread-3,5,main] state: RUNNABLE 11:11:28.578 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[Thread-4,5,main] state: TIMED_WAITING 11:11:29.582 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[Thread-4,5,main] state: TIMED_WAITING 11:11:30.587 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[Thread-4,5,main] state: TIMED_WAITING 11:11:31.082 [Thread-4] INFO com.learn.core.chapter14.RunnableExample - Thread[Thread-4,5,main] state: RUNNABLE 11:11:31.590 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[Thread-4,5,] state: TERMINATED
可以看到,线程[Thread-4]状态流转为:RUNNABLE -> BLOCKED -> TIMED_WAITING -> RUNNABLE -> TERMINATED。
上面为了模拟获取锁,线程需要休眠,所以状态中出现了TIMED_WAITING状态,如果没有调用休眠函数的话,状态流转就简化为:RUNNABLE -> BLOCKED -> RUNNABLE -> TERMINATED。
-
WAITING
:等待状态Thread state for a waiting thread. A thread is in the waiting state due to calling one of the following methods:
Object#wait with no timeout
Thread#join with no timeout
LockSupport#park
A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.上面提到会有3种情况导致线程从RUNNABLE状态进入WAITING状态,执行对应的操作又可以将线程从WAITING状态转换为RUNNABLE状态:
Enter WAITING Exit WAITING Description Object.wait() Object.notify(), Object.notifyAll() 线程先获取到互斥锁(所以需要在synchronized代码块中使用,否则会抛异常IllegalMonitorStateException),若条件不满足,则释放互斥锁,进入等待状态(调用wait方法);当等待的条件满足时,通知等待的线程(调用notify方法),重新去获取互斥锁。 Thread.join() 被等待的线程执行完成,等待的线程才继续执行 用于线程同步,比如main线程等待子线程执行完成 LockSupport.park() LockSupport.unpark(Thread thread);其他线程中断了当前线程;当前线程出现了不可预知问题 park方法使当前线程进入等待状态,不会释放锁资源;unpark相当于提供一个许可凭证,如果线程启动后,先调用的unpark方法,那么当该线程后面调park方法时会立刻返回,因为它早就拿到了许可证。 我们先通过一个示例来演示一下Thread.join()和LockSupport.park():
@Log4j2 public class RunnableExample implements Runnable { @Override public void run() { log.info("in run..."); // synchronized (RunnableExample.class) { // ② LockSupport.park(); // } log.info("{} state: {}", Thread.currentThread(), Thread.currentThread().getState()); } } @Log4j2 public class ThreadCreateTest { @Test public void testThreadState() throws InterruptedException { RunnableExample runnableExample = new RunnableExample(); Thread thread1 = new Thread(runnableExample, "thread1"); thread1.start(); Thread.sleep(100); displayThreadInfo(thread1); // LockSupport.unpark(thread1); // ① thread1.join(); } }
这里我们先将①处的unpark方法注释掉,观察一下:
16:52:03.575 [thread1] INFO com.learn.core.chapter14.RunnableExample - in run... 16:52:03.674 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,main] state: WAITING
打印上面两行日志后,程序进入等待中,不再输出日志,我们通过jstack打印一下当前的线程状态:
➜ vifile >jps 29072 GradleWorkerMain 4146 26403 GradleDaemon 4053 MacLauncher 27925 GradleDaemon 29100 Jps ➜ vifile >jstack 29072 2021-02-25 16:53:49 Full thread dump Java HotSpot(TM) 64-Bit Server VM (11.0.7+8-LTS mixed mode): Threads class SMR info: _java_thread_list=0x00007f8754e09530, length=14, elements={ 0x00007f875580f000, 0x00007f875980f800, 0x00007f8756020800, 0x00007f8759813800, 0x00007f875a00b000, 0x00007f8756022800, 0x00007f875602b800, 0x00007f8759829000, 0x00007f875590a800, 0x00007f875591e000, 0x00007f87561f4000, 0x00007f8755107800, 0x00007f87550ad000, 0x00007f8757888800 } ... ... "Test worker" #13 prio=5 os_prio=31 cpu=286.95ms elapsed=106.36s tid=0x00007f875591e000 nid=0xa003 in Object.wait() [0x0000700003f1c000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(java.base@11.0.7/Native Method) - waiting on <0x00000007efa60480> (a java.lang.Thread) at java.lang.Thread.join(java.base@11.0.7/Thread.java:1305) - waiting to re-lock in wait() <0x00000007efa60480> (a java.lang.Thread) at java.lang.Thread.join(java.base@11.0.7/Thread.java:1379) at com.learn.core.chapter14.ThreadCreateTest.testThreadState(ThreadCreateTest.java:44) at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@11.0.7/Native Method) at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@11.0.7/NativeMethodAccessorImpl.java:62) at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@11.0.7/DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(java.base@11.0.7/Method.java:566) ... ... "thread1" #16 prio=5 os_prio=31 cpu=6.84ms elapsed=106.03s tid=0x00007f87550ad000 nid=0x6a03 waiting on condition [0x000070000493e000] java.lang.Thread.State: WAITING (parking) at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method) at java.util.concurrent.locks.LockSupport.park(java.base@11.0.7/LockSupport.java:323) at com.learn.core.chapter14.RunnableExample.run(RunnableExample.java:18) at java.lang.Thread.run(java.base@11.0.7/Thread.java:834) ... ... JNI global refs: 15, weak refs: 0
很明显了,[Test worker]和[thread1]线程都是WAITING状态,并且对应的join和park方法也有打印。
接着,我们放开①处的unpark代码,输出结果如下:
17:04:52.962 [thread1] INFO com.learn.core.chapter14.RunnableExample - in run... 17:04:53.059 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,main] state: WAITING 17:04:53.059 [thread1] INFO com.learn.core.chapter14.RunnableExample - Thread[thread1,5,main] state: RUNNABLE
另外补充说明一下,上面第②处注释掉的代码,如果一个线程获取到synchronized隐式锁之后,再调用LockSupport.park()方法,后续线程进入run方法后,会等待synchronized隐式锁释放,进入BLOCKED状态。
最后,我们通过一个资源分配器测试一下wait(等待)~ notify(通知)组合:
/** * @author Demon.Lee * @date 2021-02-26 10:44 * @desc 资源分配器 */ @Log4j2 public class ResourceAllocator { private final List<String> locks; private ResourceAllocator() { locks = new ArrayList<>(); } public synchronized void reserve(String resource) { // ① 这里是while条件判断,而不是if,因为线程从等待被唤醒后,需要重新判断条件是否满足 while (locks.contains(resource)) { log.info("reserve [{}] blocking, waiting now...", resource); try { // synchronized获得的是哪个对象上的锁,就调用哪个对象的wait和notify方法 this.wait(); // 唤醒之后继续运行后面的代码 log.info("I am wake up, reserve [{}] again", resource); } catch (InterruptedException e) { log.error("reserve InterruptedException: ", e); } } log.info("reserve [{}] success...locks: {}", resource, locks); locks.add(resource); } public synchronized void release(String resource) { log.info("release [{}] now...", resource); locks.remove(resource); // ② 没有特殊情况,都使用notifyAll() this.notifyAll(); } private static class ResourceAllocatorInner { private static final ResourceAllocator INSTANCE = new ResourceAllocator(); } public static ResourceAllocator getInstance() { return ResourceAllocatorInner.INSTANCE; } } @Log4j2 public class MultiThreadsConcurrencyTest { @Test public void testWaitNotify() { ResourceAllocator allocator = ResourceAllocator.getInstance(); List<Thread> threads = new ArrayList<>(); String hello = "Hello World"; IntStream.rangeClosed(1, 3).forEach(i -> { Thread thread = new Thread(() -> { allocator.reserve(hello); sleep(3000); allocator.release(hello); }, "thread-" + i); thread.start(); threads.add(thread); }); sleep(300); displayThreadInfo(threads); threads.forEach(this::join); } private void displayThreadInfo(List<Thread> threads) { for (Thread thread : threads) { log.info("{} state: {}", thread, thread.getState()); } } private void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } private void join(Thread thread) { log.info("thread [{}] join", thread.getName()); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
打印日志内容如下:
17:32:10.985 [thread-1] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] success...locks: [] 17:32:10.994 [thread-3] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] blocking, waiting now... 17:32:10.995 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] blocking, waiting now... 17:32:11.282 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - Thread[thread-1,5,main] state: TIMED_WAITING 17:32:11.283 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - Thread[thread-2,5,main] state: WAITING 17:32:11.283 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - Thread[thread-3,5,main] state: WAITING 17:32:11.284 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - thread [thread-1] join 17:32:13.998 [thread-1] INFO com.learn.core.chapter14.ResourceAllocator - release [Hello World] now... 17:32:13.998 [thread-3] INFO com.learn.core.chapter14.ResourceAllocator - I am wake up, reserve [Hello World] again, locks: [] 17:32:13.998 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - thread [thread-2] join 17:32:13.999 [thread-3] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] success...locks: [] 17:32:13.999 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - I am wake up, reserve [Hello World] again, locks: [Hello World] 17:32:13.999 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] blocking, waiting now... 17:32:17.004 [thread-3] INFO com.learn.core.chapter14.ResourceAllocator - release [Hello World] now... 17:32:17.004 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - I am wake up, reserve [Hello World] again, locks: [] 17:32:17.005 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] success...locks: [] 17:32:20.007 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - release [Hello World] now... 17:32:20.008 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - thread [thread-3] join
通过日志可以发现,[thread-1]先申请到资源,[thread-2、thread-3]两个线程直接进入WAITING状态,当[thread-1]释放资源后,调用notifyAll()后,[thread-2、thread-3]都被唤醒,但[thread-3]先抢到了锁,而[thread-2]后续拿到[thread-3]释放的锁,但因为判断条件不满足,又进入了WAITING状态。
这里有两个需要注意的地方:
1)代码①处的条件判断必须是while,而不能是if,换成if之后,日志输出如下:
17:50:41.522 [thread-1] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] success...locks: [] 17:50:41.528 [thread-3] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] blocking, waiting now... 17:50:41.528 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] blocking, waiting now... 17:50:41.819 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - Thread[thread-1,5,main] state: TIMED_WAITING 17:50:41.819 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - Thread[thread-2,5,main] state: WAITING 17:50:41.819 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - Thread[thread-3,5,main] state: WAITING 17:50:41.820 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - thread [thread-1] join 17:50:44.530 [thread-1] INFO com.learn.core.chapter14.ResourceAllocator - release [Hello World] now... 17:50:44.530 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - thread [thread-2] join 17:50:44.530 [thread-3] INFO com.learn.core.chapter14.ResourceAllocator - I am wake up, reserve [Hello World] again, locks: [] 17:50:44.531 [thread-3] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] success...locks: [] 17:50:44.531 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - I am wake up, reserve [Hello World] again, locks: [Hello World] 17:50:44.532 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - reserve [Hello World] success...locks: [Hello World] 17:50:47.537 [thread-3] INFO com.learn.core.chapter14.ResourceAllocator - release [Hello World] now... 17:50:47.538 [thread-2] INFO com.learn.core.chapter14.ResourceAllocator - release [Hello World] now... 17:50:47.538 [Test worker] INFO com.learn.core.chapter14.MultiThreadsConcurrencyTest - thread [thread-3] join
很显然,[thread-3]拿到锁,申请到资源,释放了synchronized隐式锁,紧接着[thread-2]拿到synchronized隐式锁,由于if不会重复判断,所以又申请到了资源,但此时[thread-3]并没有释放该资源,这就造成了两个线程都申请到了共享资源,造成后续不可预知的问题。
2)为啥是notifyAll,而不是notify?
当调用某一对象的wait()方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。
notify只从对象等待池中移出一个线程到锁标志等待池中,所以可能会导致某些线程永远也等不到。举个例子:线程1,3申请资源A,线程2,4申请资源B,如果线程1和2都先申请到了,那么线程3和4就会等待。当线程1释放资源A,调用notify()后,如果是线程4被通知到,此时线程2依然未释放资源,所以线程4会继续等待,而后面线程2只有一次notify(),但还有两个线程(线程3和线程4)在等待,所以其中一个线程可能就再也没有机会被唤醒了。
-
TIMED_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:
Thread#sleep
Object#wait with timeout
Thread#join with timeout
LockSupport#parkNanos
LockSupport#parkUntilTIMED_WAITING与WAITING状态的差别在于,WAITING需要等待唤醒,而TIMED_WAITING是等待唤醒或等待的时间到了自动被唤醒。所以上面的五种方式中有四种与前面WAITING的三种方式相对应,这里就不再一一举例验证了,仅用LockSupport.parkUntil测试说明。
@Log4j2 public class RunnableExample implements Runnable { @Override public void run() { log.info("in run..."); Instant now = Instant.now(); Instant threeSecondsLater = now.plusSeconds(3); // LockSupport.parkNanos(this, 1000_000_000L * 3); LockSupport.parkUntil(this, threeSecondsLater.toEpochMilli()); log.info("{} state: {}", Thread.currentThread(), Thread.currentThread().getState()); } } @Log4j2 public class ThreadCreateTest { @Test public void testThreadState() throws InterruptedException { RunnableExample runnableExample = new RunnableExample(); Thread thread1 = new Thread(runnableExample, "thread1"); thread1.start(); Thread.sleep(500); displayThreadInfo(thread1); // LockSupport.unpark(thread1); thread1.join(); // thread1.join(1000); log.info("[{}] exit...", Thread.currentThread().getName()); } }
输出结果为:
11:13:24.015 [thread1] INFO com.learn.core.chapter14.RunnableExample - in run... 11:13:24.518 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,main] state: TIMED_WAITING 11:13:27.025 [thread1] INFO com.learn.core.chapter14.RunnableExample - Thread[thread1,5,main] state: RUNNABLE 11:13:27.025 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - [Test worker] exit...
这里我并未使用LockSupport.unpark,[thread1]是自己等待时间到了之后,自动唤醒的。
新增的Thread#sleep,前面以及使用很多次了,其作用就是让当前线程进入等待,让出CPU使用权,但其与前面的Object.wait()有什么区别呢?
1)Object#wait是实例方法,必须在synchronized代码块中使用,而Thread#sleep是静态方法,任何地方都可以使用;
2)Object#wait可以指定时长,也可以不指定,但Thread#sleep必须指定;
3)Object#wait会释放锁资源,而调用Thread#sleep的线程如果占用了锁,则不会释放。
-
TERMINATED
:终止状态终止意味着线程运行结束了,不管程序是正常还是异常结束的,状态不会再有变化。
Thread state for a terminated thread.
The thread has completed execution.
那如果想提前终止一个线程该如何做呢?比如某个线程执行了很长时间都没有响应,我不想再等了。
答案是使用Thread#interrupt相关方法,而不是使用已标记为@Deprecated(since=“1.2”)的stop()方法。
Why is Thread#stop deprecated? 官方文档是这样说的:
Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked. (The monitors are unlocked as the
ThreadDeath
exception propagates up the stack.) If any of the objects previously protected by these monitors were in an inconsistent state, other threads may now view these objects in an inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other unchecked exceptions,ThreadDeath
kills threads silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days in the future.我的理解是:Thread#stop方法会强制并静悄悄的将线程杀死,释放该线程占用的所有锁。但线程
被杀之后没有任何后续操作(比如回滚等),这就可能会产生中间数据(比如转账只转出,还未来得及转入),造成数据不一致。这种不一致,如果通过数据比对可以发现,那还有救,更多情况下是完全查不来,只能等投诉来。
与Thread#stop方法类似被标记为@Deprecated的还有Thread#suspend、Thread#resume方法(容易造成死锁),后续的版本升级中,这些方法将会被移除。
我们通过一个示例来说明一下数据不一致性:
@Log4j2 public class RunnableExample2 implements Runnable { private final Lock lock = new ReentrantLock(); // 共享数据,run方法中先增1,再减1 private int sharedNum = 0; @Override public void run() { lock.lock(); try { log.info("---------Thread stop example...{}", sharedNum); sharedNum++; try { Thread.sleep(3000); } catch (InterruptedException e) { log.info("InterruptedException: {}", e.getMessage()); } sharedNum--; // ① } finally { lock.unlock(); log.info("finally..."); } log.info("run out..."); // ② } public int getSharedNum() { return sharedNum; } } @Log4j2 public class ThreadCreateTest { private void displayThreadInfo(Thread... threads) { for (Thread thread : threads) { log.info("{} state: {}", thread, thread.getState()); } } @Test public void testThreadTerminated() throws InterruptedException { // RunnableExample1 runnableExample = new RunnableExample1(); RunnableExample2 runnableExample = new RunnableExample2(); Thread thread1 = new Thread(runnableExample, "thread1"); Thread thread2 = new Thread(runnableExample, "thread2"); thread1.start(); Thread.sleep(100); thread2.start(); displayThreadInfo(thread1, thread2); log.info("stop thread1 now..."); thread1.stop(); // ③ // thread1.interrupt(); // ④ Thread.sleep(100); displayThreadInfo(thread1, thread2); thread1.join(); thread2.join(); log.info("[{}] exit...{}", Thread.currentThread().getName(), runnableExample.getSharedNum()); } }
日志输出为:
17:21:31.790 [thread1] INFO com.learn.core.chapter14.RunnableExample2 - ---------Thread stop example...0 17:21:31.889 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,main] state: TIMED_WAITING 17:21:31.890 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread2,5,main] state: WAITING 17:21:31.891 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - stop thread1 now... 17:21:31.892 [thread1] INFO com.learn.core.chapter14.RunnableExample2 - finally... 17:21:31.892 [thread2] INFO com.learn.core.chapter14.RunnableExample2 - ---------Thread stop example...1 17:21:31.996 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,] state: TERMINATED 17:21:31.997 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread2,5,main] state: TIMED_WAITING 17:21:34.894 [thread2] INFO com.learn.core.chapter14.RunnableExample2 - finally... 17:21:34.895 [thread2] INFO com.learn.core.chapter14.RunnableExample2 - run out... 17:21:34.895 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - [Test worker] exit...1
很明显,当[thread1]在TIMED_WAITING状态时被stop后,锁释放了(finally代码块执行了,①②处的代码并未执行),但共享变量sharedNum的值并未恢复到0,接着[thread2]获得锁执行程序,sharedNum的初始值是1。
那Thread#interrupt又是如何让线程终止的呢?答案是主动响应中断信号。
到这里,我们发现:
Thread#stop采用的方式是:线程被动接受kill的命运,没有任何喘息的机会。
Thread#interrupt则正好相反,线程被通知要中断,线程自己根据当前业务状态选择合适的方案回应:忽略信号、等自己当前任务执行完再退出或回滚之前的操作后再退出等等。如此一来,之前所说的数据不一致性就解决了。
Talk is cheap, 我们继续上面的例子,将单元测试中的thread1.stop()换成thread1.interrupt(),再观察日志如下:
17:28:07.807 [thread1] INFO com.learn.core.chapter14.RunnableExample2 - ---------Thread stop example...0 17:28:07.905 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,main] state: TIMED_WAITING 17:28:07.905 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread2,5,main] state: WAITING 17:28:07.906 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - thread1 now... 17:28:07.907 [thread1] INFO com.learn.core.chapter14.RunnableExample2 - InterruptedException: sleep interrupted 17:28:07.908 [thread1] INFO com.learn.core.chapter14.RunnableExample2 - finally... 17:28:07.908 [thread2] INFO com.learn.core.chapter14.RunnableExample2 - ---------Thread stop example...0 17:28:07.908 [thread1] INFO com.learn.core.chapter14.RunnableExample2 - run out... 17:28:08.011 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,] state: TERMINATED 17:28:08.011 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread2,5,main] state: TIMED_WAITING 17:28:10.909 [thread2] INFO com.learn.core.chapter14.RunnableExample2 - finally... 17:28:10.909 [thread2] INFO com.learn.core.chapter14.RunnableExample2 - run out... 17:28:10.910 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - [Test worker] exit...0
InterruptedException异常被捕获后,继续执行后续代码,然后再退出。这能说明什么呢?我们看看Thread#interrupt方法的Java-doc:
Interrupts this thread.
Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.
If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.
If this thread is blocked in an I/O operation upon an InterruptibleChannel then the channel will be closed, the thread’s interrupt status will be set, and the thread will receive a java.nio.channels.ClosedByInterruptException.
If this thread is blocked in a java.nio.channels.Selector then the thread’s interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector’s wakeup method were invoked.
If none of the previous conditions hold then this thread’s interrupt status will be set.
Interrupting a thread that is not alive need not have any effect.
Throws:
SecurityException – if the current thread cannot modify this thread.总结如下:
1)线程调用interrupt方法后,如果线程在等待状态(WAITING, TIMED_WAITING),则会收到InterruptedException,根据该异常进行后续处理;
2)如果线程在RUNNABLE状态,则需要通过Thread#isInterrupted来判断自己是否被中断了;
3)如果线程在BLOCKED状态,也可以通过Thread#isInterrupted来判断自己是否被中断了,但线程无法执行任何代码,所以需要先获取到锁,才能退出,也就是说,要先解决它等待的那个线程才行。
后面两点的示例如下:
@Log4j2 public class RunnableExample3 implements Runnable { @Override public synchronized void run() { long sum = 0; for (long i = 0; i < 1000_000_000_000L; i++) { sum += i; if (Thread.currentThread().isInterrupted()) { log.info("I am interrupted...{}", i); break; } } log.info("run out...{}", sum); } } @Log4j2 public class ThreadCreateTest { private void displayThreadInfo(Thread... threads) { for (Thread thread : threads) { log.info("{} state: {}", thread, thread.getState()); } } @Test public void testThreadTerminated() throws InterruptedException { RunnableExample3 runnableExample = new RunnableExample3(); Thread thread1 = new Thread(runnableExample, "thread1"); Thread thread2 = new Thread(runnableExample, "thread2"); thread1.start(); Thread.sleep(500); thread2.start(); Thread.sleep(100); displayThreadInfo(thread1, thread2); log.info("interrupt thread2 now..."); thread2.interrupt(); Thread.sleep(100); displayThreadInfo(thread1, thread2); log.info("interrupt thread1 now..."); thread1.interrupt(); Thread.sleep(100); displayThreadInfo(thread1, thread2); thread1.join(); thread2.join(); log.info("[{}] exit...", Thread.currentThread().getName()); } }
输出结果为:
18:18:35.638 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,main] state: RUNNABLE 18:18:35.644 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread2,5,main] state: BLOCKED 18:18:35.645 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - interrupt thread2 now... 18:18:35.746 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,main] state: RUNNABLE 18:18:35.747 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread2,5,main] state: BLOCKED 18:18:35.747 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - interrupt thread1 now... 18:18:35.747 [thread1] INFO com.learn.core.chapter14.RunnableExample3 - I am interrupted...757984086 18:18:35.748 [thread1] INFO com.learn.core.chapter14.RunnableExample3 - run out...287269937693619741 18:18:35.750 [thread2] INFO com.learn.core.chapter14.RunnableExample3 - I am interrupted...0 18:18:35.750 [thread2] INFO com.learn.core.chapter14.RunnableExample3 - run out...0 18:18:35.848 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread1,5,] state: TERMINATED 18:18:35.848 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - Thread[thread2,5,] state: TERMINATED 18:18:35.848 [Test worker] INFO com.learn.core.chapter14.ThreadCreateTest - [Test worker] exit...
线程状态机
该图与前面通用线程状态机进行对比可以发现:
1)Java线程中的RUNNABLE对应了RUNNABLE和RUNNING,相当于合并了状态,而Java线程中的WAITING、TIMED WAITING和BLOCKED 3类状态则对应SLEEPING,相当于细化了状态;
2)Java线程中的RUNNABLE表示不关注底层OS的调度细节,从JVM统一表示为可执行。
另外补充说明一下:
而我们平时所谓的 Java 在调用阻塞式 API 时,线程会阻塞,指的是操作系统线程的状态,并不是 Java 线程的状态,Java线程的状态还是RUNNABLE。
需要注意的坑
Thread#sleep等方法抛出InterruptException时会将interrupted status清空,此时调用Thread#isInterrupted方法将返回false。
public class Thread implements Runnable {
...
...
/**
* 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;
...
...
}
请注意这句话:
if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
所以下面的代码是有问题的,可能永远也无法退出循环,解决方案是将①处代码放开。
@Log4j2
public class RunnableExample4 implements Runnable {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
int sum = 0;
while (true) {
sum++;
if (currentThread.isInterrupted()) {
log.info("I am interrupted...{}", sum);
break;
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
log.error("InterruptedException: {}", e.getMessage());
// currentThread.interrupt(); // ①
}
}
log.info("run out...");
}
}
参考资料
[1] 王宝令. Java线程的生命周期
[2] Cay S.Horstmann. Java核心技术·卷I
[3] 臧萌. Java入门1•2•3
[4] Java-doc. Java Thread Primitive Deprecation
[5] 杨晓峰. 一个线程两次调用start()方法会出现什么情况
[6] 武哥聊编程. 线程有哪些状态,彼此之间如何切换