Open liangcmwn opened 5 years ago
sleep和wait都可以让线程阻塞,也都可以指定超时时间,甚至还都会抛出中断异常InterruptedException。
而它们最大的区别就在于,sleep时线程依然持有锁,别人无法进当前同步方法;wait时放弃了持有的锁,其它线程有机会进入该同步方法。多次提到同步方法,因为wait必须在synchronized同步代码块中,否则会抛出异常IllegalMonitorStateException,notify也是如此,可以说wait和notify是就是为了在同步代码中做线程调度而生的。
下面一个简单的例子展现sleep和wait的区别:
// 日志行号记录
private AtomicInteger count = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Main main = new Main();
// 开启两个线程去执行test方法
new Thread(main::test).start();
new Thread(main::test).start();
}
private synchronized void test() {
try {
log("进入了同步方法,并开始睡觉,1s");
// sleep不会释放锁,因此其他线程不能进入这个方法
Thread.sleep(1000);
log("睡好了,但没事做,有事叫我,等待2s");
//阻塞在此,并且释放锁,其它线程可以进入这个方法
//当其它线程调用此对象的notify或者notifyAll时才有机会停止阻塞
//就算没有人notify,如果超时了也会停止阻塞
wait(2000);
log("我要走了,但我要再睡一觉,10s");
//这里睡的时间很长,因为没有释放锁,其它线程就算wait超时了也无法继续执行
Thread.sleep(10000);
log("走了");
notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印日志
private void log(String s) {
System.out.println(count.incrementAndGet() + " "
+ new Date().toString().split(" ")[3]
+ "\t" + Thread.currentThread().getName() + " " + s);
}
同样是唤醒等待的线程,同样最多只有一个线程能获得锁,同样不能控制哪个线程获得锁。
区别在于:
notify:唤醒一个线程,其他线程依然处于wait的等待唤醒状态,如果被唤醒的线程结束时没调用notify,其他线程就永远没人去唤醒,只能等待超时,或者被中断
notifyAll:所有线程退出wait的状态,开始竞争锁,但只有一个线程能抢到,这个线程执行完后,其他线程又会有一个幸运儿脱颖而出得到锁
private AtomicInteger count = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Main main = new Main();
// 开启两个线程去执行test方法
for (int i = 0; i < 10; i++) {
new Thread(main::testWait).start();
}
Thread.sleep(1000);
for (int i = 0; i < 5; i++) {
main.testNotify();
}
}
private synchronized void testWait() {
try {
log("进入了同步方法,开始wait");
wait();
log("wait结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private synchronized void testNotify() {
notify();
}
private void log(String s) {
System.out.println(count.incrementAndGet() + " "
+ new Date().toString().split(" ")[3]
+ "\t" + Thread.currentThread().getName() + " " + s);
}
例子中有10个线程在wait,但notify了5次,然后其它线程一直阻塞,这也就说明使用notify时如果不能准确控制和wait的线程数对应,可能会导致某些线程永远阻塞。
使用notifyAll唤醒所有等待的线程: 只需要调用一次notifyAll,所有的等待线程都被唤醒,并且去竞争锁,然后依次(无序)获取锁完成了后续任务。
一些源码中出现wait时,往往都是伴随着一个循环语句出现的,比如:
private synchronized void f() throws InterruptedException {
while (!isOk()) {
wait();
}
System.out.println("I'm ok");
}
既然wait会被阻塞直到被唤醒,那么用if+wait不就可以了吗?其他线程发现条件达到时notify一下不就行了?
理想情况确实如此,但实际开发中我们往往不能保证这个线程被notify时条件已经满足了,因为很可能有某个无关(和这个条件的逻辑无关)的线程因为需要线程调度而调用了notify或者notifyAll。此时如果样例中位置等待的线程不巧被唤醒,它就会继续往下执行,但因为用的if,这次被唤醒就不会再判断条件是否满足,最终程序按照我们不期望的方式执行下去。
wait: 释放当前锁,阻塞直到被notify或notifyAll唤醒,或者超时,或者线程被中断(InterruptedException)
notify: 任意选择一个(无法控制选哪个)正在这个对象上等待的线程把它唤醒,其它线程依然在等待被唤醒
notifyAll: 唤醒所有线程,让它们去竞争,不过也只有一个能抢到锁
sleep: 不是Object中的方法,而是Thread类的静态方法,让当前线程持有锁阻塞指定时间