线程等待唤醒的三种方法
需求:我们实现A线程等待B线程执行完在执行。
Object下面的wait()和notify()
使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(() -> {
synchronized (o) {
log.info("准备执行A");
try {
o.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("A执行完成");
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized (o) {
log.info("我是B,我要唤醒A");
o.notify();
}
}, "B").start();
}
运行:
15:36:43.390 [A] INFO com.yougong.digitalwallet.controller.TestTest - 准备执行A
15:36:44.395 [B] INFO com.yougong.digitalwallet.controller.TestTest - 我是B,我要唤醒A
15:36:44.395 [A] INFO com.yougong.digitalwallet.controller.TestTest - A执行完成
思考1
- 如果去掉
synchronized
,再次执行
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(() -> {
//synchronized (o) {
log.info("准备执行A");
try {
o.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("A执行完成");
//}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
//synchronized (o) {
log.info("我是B,我要唤醒A");
o.notify();
//}
}, "B").start();
}
结果:
15:38:58.016 [A] INFO com.yougong.digitalwallet.controller.TestTest - 准备执行A
Exception in thread "A" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.yougong.digitalwallet.controller.TestTest.lambda$main$0(TestTest.java:22)
at java.lang.Thread.run(Thread.java:748)
15:38:59.029 [B] INFO com.yougong.digitalwallet.controller.TestTest - 我是B,我要唤醒A
Exception in thread "B" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.yougong.digitalwallet.controller.TestTest.lambda$main$1(TestTest.java:35)
at java.lang.Thread.run(Thread.java:748)
思考2
先 notify 再 wait 会怎么
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o) {
log.info("准备执行A");
try {
o.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("A执行完成");
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized (o) {
log.info("我是B,我要唤醒A");
o.notify();
}
}, "B").start();
}
发现:程序无法执行,无法唤醒
总结
-
wait和notify方法必须要在同步块或者方法里面,且成对出现使用
-
先wait后notify才OK
Condition类的await()和single()方法
使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
log.info("准备执行A");
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
log.info("A执行完成");
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
lock.lock();
try {
log.info("我是B,我要唤醒A");
condition.signal();
} finally {
lock.unlock();
}
}, "B").start();
}
运行:
15:45:28.053 [A] INFO com.yougong.digitalwallet.controller.TestTest - 准备执行A
15:45:29.056 [B] INFO com.yougong.digitalwallet.controller.TestTest - 我是B,我要唤醒A
15:45:29.056 [A] INFO com.yougong.digitalwallet.controller.TestTest - A执行完成
思考1
如果去掉 lock
,再次执行
public static void main(String[] args) throws InterruptedException {
//m1();
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
log.info("准备执行A");
//lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//lock.unlock();
}
log.info("A执行完成");
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
//lock.lock();
try {
log.info("我是B,我要唤醒A");
condition.signal();
} finally {
//lock.unlock();
}
}, "B").start();
}
运行:
15:48:05.392 [A] INFO com.yougong.digitalwallet.controller.TestTest - 准备执行A
Exception in thread "A" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
at com.yougong.digitalwallet.controller.TestTest.lambda$main$0(TestTest.java:28)
at java.lang.Thread.run(Thread.java:748)
15:48:06.404 [B] INFO com.yougong.digitalwallet.controller.TestTest - 我是B,我要唤醒A
Exception in thread "B" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at com.yougong.digitalwallet.controller.TestTest.lambda$main$1(TestTest.java:44)
at java.lang.Thread.run(Thread.java:748)
思考2
先 signal 再 await 会怎么
代码:
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("准备执行A");
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
log.info("A执行完成");
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
lock.lock();
try {
log.info("我是B,我要唤醒A");
condition.signal();
} finally {
lock.unlock();
}
}, "B").start();
}
运行:
总结
-
await和single方法必须要在同步块或者方法里面,且成对出现使用
-
先await后single才OK
LockSupport类的park()和unpark()方法
LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
LockSupport类使用的是一种名为 Permit(许可)的概念做到阻塞和唤醒线程的,每一个线程都有一个许可(Permit),permit只存在两个值0或者1,默认为0.
调用LockSupport.park()时,当前线程就会阻塞,因为permit默认为0,直到别的线程将当前线程的permit设置成1时,park方法才会被唤醒,然后会将permit设置为0,继续执行park后面的代码。
调用LockSupport的unpark(thread)方法时,会将thread线程的permit就会被设置成1,然后自动唤醒thread线程继续执行之前阻塞的park方法后面的代码。注意:unpark(thread)无论执行多少遍,permit的值只可能是1,不会依次累加。
代码:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
log.info("准备执行A");
LockSupport.park();
log.info("A执行完成");
}, "A");
thread.start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
log.info("我是B,我要唤醒A");
LockSupport.unpark(thread);
}, "B").start();
}
运行:
16:12:03.318 [A] INFO com.yougong.digitalwallet.controller.TestTest - 准备执行A
16:12:04.320 [B] INFO com.yougong.digitalwallet.controller.TestTest - 我是B,我要唤醒A
16:12:04.320 [A] INFO com.yougong.digitalwallet.controller.TestTest - A执行完成
思考2
先 unpark 再 park会怎么
代码:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
log.info("准备执行A");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
LockSupport.park();
log.info("A执行完成");
}, "A");
thread.start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
log.info("我是B,我要唤醒A");
LockSupport.unpark(thread);
}, "B").start();
}
运行:
16:12:57.447 [A] INFO com.yougong.digitalwallet.controller.TestTest - 准备执行A
16:12:58.453 [B] INFO com.yougong.digitalwallet.controller.TestTest - 我是B,我要唤醒A
16:13:00.450 [A] INFO com.yougong.digitalwallet.controller.TestTest - A执行完成
没问题。。。。