深入理解Java中的wait() 方法
使用场景
当某个线程获取到锁后,发现当前还不满足执行的条件,就可以调用对象锁的wait方法,进入等待状态。
直到某个时刻,外在条件满足了,就可以由其他线程通过调用notify()或者notifyAll()方法,来唤醒此线程。
这篇文章将侧重于讨论wait()方法对于线程状态的影响,以及被唤醒后线程的状态变更。
条件
只有已经获取锁的线程,才可以调用锁的wait()、notify()方法,否则会抛出异常IllegalMonitorStateException。
比如下面的代码,A获得了锁后,主动调用wait方法释放锁和CPU资源,然后就陷入了阻塞状态。
主线程在没获得锁的情况下,直接调用notify方法,会抛出异常。
@Log4j
public class WaitTest {
public static void main(String[] args) {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
log.info("获取了锁");
try {
log.info("休眠一会儿");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("调用wait..");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("被唤醒");
}
}, "A");
threadA.start();
lock.notify();
}
}
现在我们创建一个B线程,抢占锁后,再调用notify方法。
@Log4j
public class WaitTest {
public static void main(String[] args) {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
log.info("获取了锁");
try {
log.info("休眠一会儿");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("调用wait..");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("被唤醒");
}
}, "A");
threadA.start();
Thread threadB = new Thread(()->{
synchronized (lock) {
log.info("获得了锁");
log.info("叫醒A");
lock.notify();
}
}, "B");
threadB.start();
}
}
可以唤醒A方法。
线程状态变化
首先要明确一点,线程正常运行时的状态时Runnable,调用Wait方法后,变为Waiting状态。
举例说明:
@Log4j
public class WaitTest {
public static void main(String[] args) {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
log.info("获取了锁");
try {
log.info("休眠一会儿");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("调用wait..");
try {
log.info("wait前的线程状态" + Thread.currentThread().getState());
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("被唤醒");
}
}, "A");
threadA.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("线程A wait后的状态" + threadA.getState());
}
}
运行结果:
现在的问题在于,主动wait的线程,被唤醒后,状态一定会由WAITING变为RUNNABLE吗?
我们再做一个测试。
先概括一下测试内容,A线程wait。2秒后,启动B线程,B线程再去唤醒A线程。记录唤醒前后的状态。
@Log4j
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
log.info("获取了锁");
try {
log.info("休眠一会儿");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("调用wait..");
try {
log.info("wait前的线程状态" + Thread.currentThread().getState());
lock.wait();
log.info("wait后的线程状态" + Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("被唤醒");
}
}, "A");
threadA.start();
TimeUnit.SECONDS.sleep(2);
Thread threadB = new Thread(()->{
synchronized (lock) {
log.info("获得了锁");
log.info("叫醒A前,A的状态" + threadA.getState());
log.info("叫醒A");
lock.notify();
}
}, "B");
threadB.start();
}
}
运行结果
和我们的预测是一致的。
但实际上,这段代码还是有点问题的。上述代码中的B线程调用notify方法后,立刻释放了锁,但假如B调用notify后,发现自己还有很多任务没有完成,不释放锁,A线程的状态会怎么变化呢
修改后的代码
@Log4j
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
log.info("获取了锁");
try {
log.info("休眠一会儿");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("调用wait..");
try {
log.info("wait前的线程状态" + Thread.currentThread().getState());
lock.wait();
log.info("wait后的线程状态" + Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("被唤醒");
}
}, "A");
threadA.start();
TimeUnit.SECONDS.sleep(2);
Thread threadB = new Thread(()->{
synchronized (lock) {
log.info("获得了锁");
log.info("叫醒A前,A的状态" + threadA.getState());
log.info("叫醒A");
lock.notify();
log.info("发现还有很多事需要做,先不释放锁");
log.info("我在做事过程中,A的状态: " + threadA.getState());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("我做完了");
}
}, "B");
threadB.start();
}
}
测试结果
结果中出现了BLOCKED。
也就是说B调用notify方法时,并不会把线程的控制权立刻交出去,A被唤醒后,也不会将状态变为RUNNABLE。
而是先变为BLOCKED,然后参与锁的竞争,竞争成功重新获得锁后,才会向下执行。
Java中,每一个对象都会有一个对应的Monitor锁,Monitor维护着EntrySet和WaitSet。
线程阻塞时,会被放入EntrySet,对应的状态是BLOCKED状态,
线程调用wait方法后,会被加进WaitSet中,对应的状态是WAITING、TIMED_WAITING。
上面我们也分析了,线程“被唤醒”和“获得锁”是两个过程,被唤醒的线程需要重新参与锁竞争。
如果这么理解的话,线程应该是从WaitSet中苏醒后,又被加进了EntrySet队列(因为状态变为了BLOCKED)。
但也有人觉得,从效率角度看的话,jvm没有必要做这种“队列间的移动” (观点来源:调用了wait()的线程进入等待池,只有被notify唤醒之后才进入锁池,这两个池的内涵是什么? - 知乎)
但我没发现答主摆出了什么比较靠谱的证据,所以jvm到底怎么实现的还有待考证。
有限等待
wait方法是可以给他传一个时间参数进去的,是一种自动唤醒机制:在指定时间内,如果没有其他线程唤醒自己,则主动唤醒自己。
如果传0或者不传,则表示永久等待
@Log4j
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
log.info("获取了锁");
try {
log.info("休眠一会儿");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("调用wait..");
try {
log.info("wait前的线程状态" + Thread.currentThread().getState());
lock.wait(1);
log.info("wait后的线程状态" + Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("被唤醒");
}
}, "A");
threadA.start();
}
}
结果
这是常规使用,但是按照我们前面的分析,A被唤醒后,会继续参加锁的竞争,假设在A在 wait( t )的过程中(也就是t时间之内),有其他线程抢占了线程,A还能继续往下走吗。
为了让B线程占用锁,我们把A的wait时间设置为100毫秒。
跑一下测试代码
@Log4j
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
log.info("获取了锁");
try {
log.info("休眠一会儿");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("准备调用wait..");
try {
log.info("wait前的线程状态" + Thread.currentThread().getState());
lock.wait(100);
log.info("wait后的线程状态" + Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("被唤醒");
}
}, "A");
threadA.start();
Thread threadB = new Thread(()->{
synchronized (lock) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("线程A的状态" + threadA.getState());
while (true) {
}
}
}, "B");
threadB.start();
}
}
运行结果:
即使100毫秒过了,A也不能继续向下执行,因为他只是被唤醒了,但并没有成功得获取锁,所以会进入BLOCKED状态。
更多推荐
所有评论(0)