线程状态机
WATING和TIMED_WAITING被唤醒后,是进入BLOCKED阻塞状态,进入阻塞同步队列
线程状态与锁状态关系
● 锁池状态也就是同步队列,当调用notify和notifyall时,线程只是先进入了BLOCKED状态,只有拿到对象o的锁,才能唤醒线程;当调用wait时,会释放锁,并进入等待队列,同时线程状态变为WAITING。
● wait状态唤醒有三种方式
○ being notified, 即调用notify()或notifyAll()
○ interrupted, 这种方式会抛出InterruptedException异常
○ timing out, 超时
线程通信方式-等待/通知模式
● 调用wait()方法,会释放锁,线程状态由RUNNING->WAITNG,当前线程进入对象等待队列中;
● 调用notify()/notifyAll()方法不会立马释放锁,notify()方法是将等待队列中的线程移到同步队列中,而notifyAll()则是全部移到同步队列中,被移出的线程状态WAITING–>BLOCKED;
● 当前调用notify()/notifyAll()的线程释放锁了才算释放锁,才有机会唤醒wait线程返回。
● 从wait()返回的前提是必须获得调用对象锁,也就是说notify()与notifyAll()释放锁之后,wait()进入BLOCKED状态,如果其他线程有竞争当前锁的话,wait线程继续争取锁资格。
● 使用wait()、notify()、notifyAll()方法时需要先调对象加锁(防止线程饿死)。
模板代码
static Object lock = new Object();
static boolean flag = false;
// 等待线程
synchronized(lock) {
// 1. 获取lock锁,状态为RUNNING
while(!flag) {
// 2. 进入等待队列,状态为WAITING,释放锁
// 建议多采用超时等待
lock.wait();
// 3. 获取锁,状态为RUNNING
}
// 干活。。。
}
// 通知线程
synchronized(lock) {
// 1. 获取锁,状态为RUNNING
flag = true;
// 2. 通知等待线程,等待线程进入同步队列,等待线程状态为BLOCKED
lock.notify();
}
// 3. 释放锁
简易版顺序图
- wait的执行并不一定要获取lock对象锁,那为何要用synchronized呢?因为如果wait和notify不加锁就执行,那么会存在notify先于wait执行(如下代码),从而造成wait永远没有机会唤醒,这也是为什么建议采用超时等待。
while(!flag) {
// 不允许在这里提前调用notify
lock.wait();
}
● 在有加锁的情况下,如果先执行了notify,也没事情,因为有flag来存储是否已经通知过。
● 可参考thread.join的实现
thread.join()的应用
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");
}
// 这里的while(){wait(millis)} 就是利用等待/通知中的等待模式,只不过加上了超时设置
if (millis == 0) {
// while循环,当线程还活着的时候就一直循环等待,直到线程终止
while (isAlive()) {
// 无限期wait等待,释放锁。
wait(0);
}
// 条件满足时返回
} else {
while (isAlive()) {
// 获取下一轮剩余等待时长
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
- join本质上使用了WAIT/NOTIFY通信模式,线程在死亡时,会调用“this.notifyAll”,从而唤醒join中的线程。
与LockSupport区别
- LockSupport不需存在死锁问题,如果在调用park前,已经调用了unpark,则调用park会立马返回,因为该线程已经持有许可permit,因此使用LockSupport,不需要synchronize。
- LockSupport在线程中断时,不会抛出异常,而wait会抛出InterruptedException,因此使用LockSupport时,需要在解除阻塞后,自行判断是否被中断
LockSupport.park(this);
if (Thread.interrupted()) {
// ....
throw new InterruptedException();
}