前言
本章内容涉及wait()、notify()、notifyAll()、sleep()、join()、interrupt()和对应的超时方法。
Object中的相关方法介绍
package java.lang;
public class Object {
//唤醒此对象监视器等待的单个线程,被唤醒线程进入就绪状态
public final native void notify();
//唤醒此对象监视器等待的所有线程,被唤醒线程进入就绪状态
public final native void notifyAll();
//让当前线程进入等待(阻塞)状态,超时会被唤醒
public final native void wait(long timeout) throws InterruptedException;
//纳秒大于零,毫秒数加1。如果有需求,直接把毫秒数加1调用上一个方法即可
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);
}
//让当前线程进入无限期等待(阻塞)状态
public final void wait() throws InterruptedException {
wait(0);
}
...
}
注释中已经大致说明了方法用途。
wait()、notify()
线程的等待与唤醒为什么在Object中而不在线程Thread中,需要强调的是,这里说的线程等待是指让线程等待在某一个对象的监视器上(用Object.wait()表示),等待时会释放持有该对象的同步锁,依赖于synchronized关键字使用(否则报监视器状态异常IllegalMonitorStateException)。同样,线程唤醒也是指唤醒等待在某一个对象监视器上的线程(用Object.notify()表示),也依赖于synchronized关键字。说到这里,前面提出的问题已经基本回答完了,说白了,线程的等待与唤醒都是基于某一对象的监视器,而线程本身只是其中的一种而已。代码示例:
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author HXJ
* @date 2018/7/20
*/
public class WaitNotify {
public static void main(String[] args) {
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//用于等待唤醒的对象
final Object waitAndNotify = new Object();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " notify线程开始运行!...");
//thread-notify线程在main线程等待释放同步锁之前阻塞在waitAndNotify对象的监视器上
synchronized (waitAndNotify) {
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 唤醒主线程!");
//唤醒在waitAndNotify对象监视器上等待的main线程
waitAndNotify.notify();
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 已唤醒主线程!");
}
}
}, "thread-notify");
synchronized (waitAndNotify) {
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " start notify线程");
thread.start();
try {
//主线程休眠两秒是为了表现thread-notify线程的阻塞等待
Thread.sleep(2 * 1000);
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 主线程!wait()");
//让线程等待在waitAndNotify对象的监视器上,并释放同步锁
waitAndNotify.wait();
} catch (InterruptedException e) {
}
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 主线程!继续运行...");
}
}
}
运行结果:
2018-07-20 17:53:06 main start notify线程
2018-07-20 17:53:06 thread-notify notify线程开始运行!...
2018-07-20 17:53:08 main 主线程!wait()
2018-07-20 17:53:08 thread-notify 唤醒主线程!
2018-07-20 17:53:08 thread-notify 已唤醒主线程!
2018-07-20 17:53:08 main 主线程!继续运行...
运行结果说明:
从代码中可以看出,主线程main先创建了一个用于等待、唤醒的对象waitAndNotify,然后再创建线程thread-notify,线程thread-notify的运行逻辑用于唤醒等待线程。接着主线程main获取waitAndNotify对象同步锁,启动线程thread-notify;由于waitAndNotify同步锁已经被main线程持有,且休眠两秒,所以线程thread-notify阻塞等待waitAndNotify对象同步锁,直到main线程休眠结束后调用wait()等待在waitAndNotify对象监视器上并释放同步锁;接着线程thread-notify取得同步锁,唤醒等待在waitAndNotify监视器上的main线程,main线程继续运行至结束。
wait(long timeout)、notifyAll()
前边介绍完wait()和notify()后,这两个函数已经很简单了,通过Object源码发现wait()也是通过调用wait(long timeout)实现的,参数为0意思是无限期等待,直到被唤醒。如果参数大于0,假如参数是500,意思是等待500毫秒,等待期间如果线程未被唤醒,则500毫秒后自动唤醒。notify()是唤醒一个等待线程,而notifyAll()是唤醒所以等待的线程。用法与notify()一致。
Thread中的sleep(long millis)、join()、interrupt()
sleep(long millis)
java.lang.Thread
/**
* 线程休眠,定义抛出中断异常
*/
public static native void sleep(long millis) throws InterruptedException;
线程休眠,Thread中的静态方法,用法比较简单,上文代码示例中已经出现过,Thread.sleep(毫秒数)即让当前运行的线程进入TIMED_WAITING (sleeping)阻塞状态,调用完成,当前线程进入休眠状态,直到休眠设置的毫秒数后由系统唤醒。需要注意的是线程与同步锁没有关系,所以不会存在等待释放同步锁一说,它可以随意的嵌入方法代码的任何地方进行调用。上文代码片段:
try {
//主线程休眠两秒是为了表现thread-notify线程的阻塞等待
Thread.sleep(2 * 1000);
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 主线程!wait()");
//让线程等待在waitAndNotify对象的监视器上,并释放同步锁
waitAndNotify.wait();
} catch (InterruptedException e) {
}
jion()
jion()线程间等待,实际上是通过Object.wait()实现的。
java.lang.Thread
/**
* 等待这个线程运行结束
*/
public final void join() throws InterruptedException {
join(0);
}
/**
* 大于等于500000纳秒或纳秒大于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);
}
/**
* 等待指定的毫秒数后,如果被等待的线程还没结束会超时自动唤醒,放弃等待
* 注意synchronized关键字,说明获取的是这个线程实例对象的同步锁,等待在这个线程实例的监视器上
*/
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) {
//参数为0,如果this线程实例(非当前执行调用线程)还活着,则等待无限期等待,直到被等待线程运行结束
while (isAlive()) {
wait(0);
}
} else {
//超时等待,等待当前线程实例运行结束或超时后系统唤醒
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
从代码中可以看出来,join()方法是让调用它的线程进行等待,等待在某一个线程实例的监视器上,说白了就是调用哪个线程实例的join()方法,就是等待该线程运行结束,或是等待超时后等待的线程继续运行。不知道大家有没有发现,Thread中的join()方法中的wait()并没有对应的notify(),被等待的线程运行结束后是怎么唤醒等待它的线程呢?其实线程运行结束退出时,jvm会执行退出线程的本地退出exit方法,执行退出逻辑。Thread.cpp中相应代码:
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we've done the notify_all below
java_lang_Thread::set_thread(threadObj(), NULL);
//此处为唤醒等待在此线程监视器上的所有线程
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
// 线程退出函数
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
...
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
//这里从命名可以看出,线程退出确保处理join相关逻辑
ensure_join(this);
...
// Remove from list of active threads list,
//and notify VM thread if we are the last non-daemon thread
Threads::remove(this);
}
代码中可以看出,线程退出逻辑中有唤醒所有等待线程的相关逻辑。
主线程等待子线程 代码示例:
public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2; i++) {
try {
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 运行中...");
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 运行结束");
}
}, "thread-sub");
//启动子线程
thread.start();
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 等待子线程运行结束...");
//等待子线程运行结束,即主线程等待在子线程实例的监视器上
thread.join();
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 继续运行...");
}
}
运行结果:
2018-07-24 16:38:56 main 等待子线程运行结束...
2018-07-24 16:38:56 thread-sub 运行中...
2018-07-24 16:38:57 thread-sub 运行中...
2018-07-24 16:38:58 thread-sub 运行结束
2018-07-24 16:38:58 main 继续运行...
从结果可以看出,主线程会等待子线程运行结束后才会继续运行。从实例代码中看,可能你会疑问,main执行子线程thread.join()调用,为什么进行等待的是main而不是thread-sub子线程呢?我们再回头看join(long millis)源码,源码中isAlive()方法在本示例代码中是判断thread-sub子线程如果还没有运行结束,在正在运行的main主线程调用wait(0)或wait(delay)进行等待。再次强调,wait()作用是让当前线程等待,也就是让main主线程等待在thread-sub子线程对象的监视器上,thread-sub子线程运行结束后再唤醒等待在自己监视器上的所有线程。
最后提个问题,如果是让主线程等待多个子线程呢?有怎么实现...
interrupt()
在《Core Java》中有这样一句话:“没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 ”。
一个现在未正常结束之前,被强制终止是很危险的事情,比如终止了一个持有锁的线程,那么有可能所有等待锁的线程都将永久阻塞。Thread中的Thread.suspend线程挂起, Thread.stop线程止等方法都被标记Deprecated弃用了。
但有时候我们确实有必要终止某一个线程,该怎么做呢,优雅的方式便是线程中断,这里要强调的是,线程中断并非线程终止,而是要给该线程发一个中断信号让它自己决定如何处理,调用某个线程interrupt()方法,会设置该线程的中断状态标识来通知它被中断了。运行中的线程只是设置了中断状态,isInterrupted()返回true;对于阻塞中的线程,收到中断信号后会产生一个中断异常InterruptedException,同时清除中断状态重新复位为false,打破阻塞状态,相当于唤醒阻塞(通过wait()、sleep()、InterruptibleChannel I/O操作、Selector阻塞)线程。并非所有阻塞状态的线程都能对中断有响应,如中断信号并不能使BLOCKED (on object monitor)状态的死锁线程恢复运行。且看源码:
java源码比较简单:
/**
* Interrupts this thread.
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
本地方法c++源码:
thread.cpp
void Thread::interrupt(Thread* thread) {
trace("interrupt", thread);
debug_only(check_for_dangling_thread_pointer(thread);)
os::interrupt(thread);
}
//windows实现,其他系统应该是一样的逻辑
os_windows.cpp
void os::interrupt(Thread* thread) {
assert(!thread->is_Java_thread() || Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
OSThread* osthread = thread->osthread();
//设置中断标识
osthread->set_interrupted(true);
// More than one thread can get here with the same value of osthread,
// resulting in multiple notifications. We do, however, want the store
// to interrupted() to be visible to other threads before we post
// the interrupt event.
OrderAccess::release();
SetEvent(osthread->interrupt_event());
// For JSR166: unpark after setting status
//设置中断标识后unpark()唤醒线程
if (thread->is_Java_thread())
((JavaThread*)thread)->parker()->unpark();
ParkEvent * ev = thread->_ParkEvent ;
if (ev != NULL) ev->unpark() ;
}
源码中有两行关键的代码
osthread->set_interrupted(true);
设置中断标识
if (thread->is_Java_thread()) ((JavaThread*)thread)->parker()->unpark();
//唤醒阻塞线程
java中调用interrupt()方法后,并不能中断一个正在运行的线程。实际上是设置了线程的中断标志位,在线程阻塞的地方(如调用sleep、wait、join等地方)抛出一个异常InterruptedException,并且中断状态也将被清除,重新复位为false,这样线程就得以退出阻塞的状态。
经典代码示例:
public void run() {
try {
....
while (!Thread.currentThread().isInterrupted() && more work to do){
// do more work;
}
} catch (InterruptedException e) {
// thread was interrupted during sleep or wait
} finally {
// cleanup, if required
}
}
代码中,线程不停的检查自身的中断状态作为while循环的条件,当线程的Thread.interrupt方法被其他线程调用,中断状态被设置为true时,退出循环正常结束运行。这说明,interrupt中断只是线程退出逻辑的一部分,前提是线程需要通过isInterrupted()检查自己的中断状态。
中断阻塞状态
对于阻塞状态(常见的通过wiat、sleep等方法进行阻塞,这些能抛出InterruptedException异常)的线程,中断信号会产生一个中断异常,使线程从阻塞状态中恢复运行,换句话说就是阻塞被中断了,线程被唤醒了。抛出InterruptedException中断异常后,线程中断状态复位false。中断异常也是线程退出逻辑的一部分。
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author HXJ
* @date 2018/7/26
*/
public class ThreadInterrupt {
public static void main(String[] args) {
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//检查自身中断状态
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " sleep ...");
Thread.sleep(10 * 1000);
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " sleep 结束");
} catch (InterruptedException e) {
//InterruptedException中断异常会复位中断状态为false
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 产生中断异常!");
}
}
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//检查自身中断状态
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " sleep ...");
Thread.sleep(10 * 1000);
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " sleep 结束");
} catch (InterruptedException e) {
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 产生中断异常!");
//InterruptedException中断异常会复位中断状态为false,所以需要重新设置中断状态
Thread.currentThread().interrupt();
}
}
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 正常退出!");
}
});
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " 启动子线程!");
thread.start();
thread1.start();
try {
Thread.sleep(2000);
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " sleep 2s 结束,中断阻塞子线程!");
thread.interrupt();
thread1.interrupt();
thread.join();
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
2018-07-26 14:59:44 main 启动子线程!
2018-07-26 14:59:44 Thread-0 sleep ...
2018-07-26 14:59:44 Thread-1 sleep ...
2018-07-26 14:59:46 main sleep 2s 结束,中断阻塞子线程!
2018-07-26 14:59:46 Thread-1 产生中断异常!
2018-07-26 14:59:46 Thread-0 产生中断异常!
2018-07-26 14:59:46 Thread-1 正常退出!
2018-07-26 14:59:46 Thread-0 sleep ...
2018-07-26 14:59:56 Thread-0 sleep 结束
2018-07-26 14:59:56 Thread-0 sleep ...
结果说明,主线程启动thread、thread1两个子线程后,开始休眠;两个子线程启动后也进入长达10s的休眠状态;主线程2s后休眠结束,分别中断了休眠阻塞状态的两个子线程,两个子线程产生中断异常恢复运行,提前结束休眠状态。由于产生中断异常后中断状态复位,所以Thread-0子线程的while条件isInterrupted()仍满足条件继续执行;而Thread-1在中断状态复位后interrupt()重新设置中断状态,while条件不满足,线程正常退出。产生中断异常后,也可以通过break直接退出循环体。