Java中线程的状态分为 6 种:
使用场景
获取到某个线程之后,发现当前并不需要使用该线程,则可以调用wait() 方法,使其进入等待过程。
等到某个时刻,满足条件之后,则可以使用notify() 或者 notifyAll() 方法来唤醒这个线程。
使用条件
对于已经获取到锁的线程才可以调用锁的wait(),notify()方法,否则会抛出IllegalMonitorStateException 异常。
比如下面的代码,A获得了锁之后,主动调用 wait() 方法释放锁和CPU资源,然后就进入了阻塞状态。
主线程在没有获得锁的情况之下,直接调用notify() 方法会抛出异常。
public class WaitTest {
public static void main(String[] args) {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
System.out.println("获取到锁");
try {
System.out.println("休眠一会");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用wait");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("被唤醒");
}
}, "A");
threadA.start();
lock.notify();
}
}
输出结果:
现在我们创建一个线程B,抢占锁后,再调用 notify() 方法
public class WaitTest {
public static void main(String[] args) {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
System.out.println("A 获取到锁");
try {
System.out.println("休眠一会");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用wait");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A 被唤醒");
}
}, "A");
threadA.start();
Thread threadB = new Thread(() -> {
synchronized (lock) {
System.out.println("B 获取了锁");
System.out.println("B 唤醒 A");
lock.notify();
}
},"B");
threadB.start();
}
}
首先我们需要知道,线程正常运行时的状态是 RUNNABLE,调用 wait() 方法后,变为 WAITING 状态。
举例说明:
public class WaitTest {
public static void main(String[] args) {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
System.out.println("A 获取到锁");
try {
System.out.println("休眠一会");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用wait");
try {
System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A 被唤醒");
}
}, "A");
threadA.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用wait后的线程状态:"+threadA.getState());
}
}
输出结果:
提问:主动 wait 的线程,被唤醒之后,它的状态一定是从 WAITING 变成 RUNNABLE 吗?
我们再做一个测试:A线程wait,2秒后,启动B线程,B线程去唤醒A线程。记录下唤醒前后的状态。
public class WaitTest {
public static void main(String[] args) {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
System.out.println("A 获取到锁");
try {
System.out.println("休眠一会");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用wait");
try {
System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
lock.wait();
System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A 被唤醒");
}
}, "A");
threadA.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread threadB = new Thread(() -> {
synchronized (lock) {
System.out.println("B 获取到锁");
System.out.println("叫醒A前,A的状态:"+threadA.getState());
System.out.println("叫醒A");
lock.notify();
}
},"B");
threadB.start();
}
}
输出结果:
上面的例子与我们的猜测是一致的。
但是,这段代码并不严谨,上述代码的 B线程在调用了 notify() 方法之后,立即释放了锁,但假如 B线程调用 notify() 后,还有很多任务没有完成,不立即释放锁,A线程的状态又会有什么变化?
修改后的代码:
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
System.out.println("A 获取到锁");
try {
System.out.println("休眠一会");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用wait");
try {
System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
lock.wait();
System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A 被唤醒");
}
}, "A");
threadA.start();
TimeUnit.SECONDS.sleep(2);
Thread threadB = new Thread(() -> {
synchronized (lock) {
System.out.println("B 获取到锁");
System.out.println("叫醒A前,A的状态:"+threadA.getState());
System.out.println("叫醒A");
lock.notify();
System.out.println("发现还有很多事要做,先不释放锁");
System.out.println("B 在做其他事的时候,A 的状态:"+threadA.getState());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B 的事情都做完了");
}
},"B");
threadB.start();
}
}
输出结果:
通过观察我们发现,A 的状态并没有立刻变成 RUNNALBE,而是 BLOCKED,等到 B线程任务处理完释放锁之后,A 的状态才变成 RUNNALBE。
也就是说,B调用完 notify() 方法后,并不会立刻把线程的控制权交出去,A 被唤醒后,也不会立即将状态变成 RUNNABLE,而是先变成 BLOCKED,然后参与锁竞争,竞争成功重新获得锁之后,再向下执行。
Java中,每一个对象都有一个对应的 Monitor 锁,Monitor维护着 EntrySet 和 WaitSet。线程阻塞时,对象会被放入到 EntrySet 中,对应的状态是 BLOCKED。线程调用wait方法后,会被加入到WaitSet中,对应的状态是WAITING、TIMED_WAITIING。
上面我们分析过,线程“被唤醒”和“获得锁”是两个过程,被唤醒的线程需要重新参与锁竞争。
这么理解的话,那么线程应该是从 WaitSet 中苏醒后,又被加入到 EntrySet 队列。
wait() 方法可以传入一个时间参数,是一种自动唤醒机制:在指定的时间内,如果没有其他线程唤醒自己,则主动唤醒自己。传 0 或者不传,则表示永久等待。
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
System.out.println("A 获取到锁");
try {
System.out.println("休眠一会");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用wait");
try {
System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
System.out.println("当前时间:"+System.currentTimeMillis());
lock.wait(1000);
System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());
System.out.println("当前时间:"+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A 被唤醒");
}
}, "A");
threadA.start();
}
}
输出结果:
这只是一种情况,如果 A 被唤醒后,继续参加锁的竞争,假设 A 在wait(t) 的过程中,有其他线程抢占了线程,A还能继续执行吗?
以下是代码示例:
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
System.out.println("A 获取到锁");
try {
System.out.println("休眠一会");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用wait");
try {
System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
System.out.println("当前时间:"+System.currentTimeMillis());
lock.wait(100);
System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());
System.out.println("当前时间:"+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A 被唤醒");
}
}, "A");
threadA.start();
Thread threadB = new Thread(() -> {
synchronized (lock) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A 的状态:"+threadA.getState());
while (true) {
}
}
},"B");
threadB.start();
}
}
输出结果:
即使 1000ms 过了,A 也不能继续执行,因为 A 只是被唤醒了,但是并没有成功获得锁,所以会进入 BLOCKED 状态。
Java中的线程休眠可以使用 Thread 类的静态方法 sleep()实现,该方法可以让当前线程暂停执行一段时间,以等待其他线程完成某些操作,或者为了节省系统资源而暂停线程执行。
代码示例:
public class SleepThreadExample {
public static void main(String[] args) {
System.out.println("线程开始执行");
try {
Thread.sleep(5000); // 休眠5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束执行");
}
}
在上面的代码中,我们使用sleep() 方法让线程休眠5 秒,然后再继续执行。如果在休眠期间线程被终端,就会抛出 InterruptedException异常。
需要注意的是,sleep() 方法会暂停当前线程的执行,因此如果在主线程中调用该方法,就会导致整个程序暂停执行,直到指定的时间到达。因此,我们应该避免在主线程中过渡使用sleep()方法,以免影响程序的响应性和用户体验。
相同点
不同点