【java高性能编程基础】 - jdk提供的三对线程通信的等待/通知机制api

x33g5p2x  于2022-01-05 转载在 Java  
字(5.3k)|赞(0)|评价(0)|浏览(643)

前言

想要实现对个线程之间的协同(如:线程的执行顺序),就涉及到线程之间的通信。线程通信有许多种方式,比如,共享变量、消息队列、文件共享等等。

下面,我们就一起来看看JDK api中提供的一种线程通信的方式——等待/通知机制。

jdk提供了三种实现线程通信的等待/通知机制:

  • suspend / resume
  • wait / notify
  • park / unpark

生产者与消费者模式是一个典型的多线程并发协作的模式,在这个模式中,一部分线程被用于去生产数据,另一部分线程去处理数据。下面就是用等待/通知机制来实现生产者消费者模式。

注意:在下面的例子中,主线程充当生产者,new Thread()的线程充当消费者的角色。

1. suspend / resume

调用suspend挂起目标线程,通过resume可以恢复线程执行。

正常执行

  1. /** 临界资源 */
  2. public static Object resource = null;
  3. public void suspendResumeTest() throws Exception {
  4. // 启动线程
  5. Thread consumerThread = new Thread(() -> {
  6. if (resource == null) { // 如果没有可用的临界资源,则进入等待
  7. System.out.println(new Date() + " 进入等待............");
  8. Thread.currentThread().suspend();
  9. }
  10. System.out.println("获取到临界资源,开始运行");
  11. });
  12. consumerThread.start();
  13. // 3秒之后,生产一个资源
  14. Thread.sleep(3000L);
  15. resource = new Object();
  16. System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
  17. consumerThread.resume();
  18. }


然而,suspend / resume这种等待/通知方式已经被弃用了,因为它非常容易写出死锁,表现在两个方面:

  • suspend不会自动释放锁,从而导致死锁;
  • 如果suspend 和 resume 的执行顺序搞反了就会导致程序永久挂起,从而形成死锁。

出现死锁

下面举例说明两种死锁的形成:

  1. suspend不会自动释放锁
  1. /** 临界资源 */
  2. public static Object resource = null;
  3. public void suspendResumeDeadLockTest() throws Exception {
  4. // 启动线程
  5. Thread consumerThread = new Thread(() -> {
  6. if (resource == null) { // 如果没有可用的临界资源,则进入等待
  7. System.out.println(new Date() + " 进入等待............");
  8. // 当前线程拿到锁,然后挂起
  9. synchronized (this) {
  10. Thread.currentThread().suspend();
  11. }
  12. }
  13. System.out.println("获取到临界资源,开始运行");
  14. });
  15. consumerThread.start();
  16. // 3秒之后,生产一个资源
  17. Thread.sleep(3000L);
  18. System.out.println(new Date());
  19. resource = new Object();
  20. // 争取到锁以后,再恢复consumerThread
  21. synchronized (this) {
  22. consumerThread.resume();
  23. }
  24. System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
  25. }

运行程序,发现程序被卡住了,线程一直被挂起,发生死锁。

  1. suspend 和 resume 的执行顺序不对
  1. /** 临界资源 */
  2. public static Object resource = null;
  3. public void suspendResumeDeadLockTest2() throws Exception {
  4. // 启动线程
  5. Thread consumerThread = new Thread(() -> {
  6. if (resource == null) {
  7. System.out.println(new Date() + " 进入等待............");
  8. try { // 为这个线程加上一点延时
  9. Thread.sleep(5000L);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. // 这里的挂起执行在resume后面
  14. Thread.currentThread().suspend();
  15. }
  16. System.out.println("获取到临界资源,开始运行");
  17. });
  18. consumerThread.start();
  19. // 3秒之后,生产一个临界资源
  20. Thread.sleep(3000L);
  21. System.out.println(new Date());
  22. resource = new Object();
  23. consumerThread.resume();
  24. System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
  25. consumerThread.join();
  26. }

suspend 和 resume 的执行顺序搞反了就会导致程序永久挂起,从而形成死锁。

已经生产了临界资源被通知消费者线程,但程序一起挂起不再运行。

2. wait / notify

wait / notify 是 suspend / resume机制的一个替代机制。

wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁,也就是wait方法会自动释放锁,这样就减小了写出死锁的可能性。

wait / notify方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出lllegalMonitorStateException异常。

正常执行

  1. /** 临界资源 */
  2. public static Object resource = null;
  3. public void waitNotifyTest() throws Exception {
  4. // 启动线程
  5. new Thread(() -> {
  6. if (resource == null) { // 如果没有可用的临界资源,则进入等待
  7. synchronized (this) {
  8. try {
  9. System.out.println(new Date() + " 进入等待............");
  10. this.wait();
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. System.out.println("获取到临界资源,开始运行");
  17. }).start();
  18. // 3秒之后,生产一个临界资源
  19. Thread.sleep(3000L);
  20. resource = new Object();
  21. synchronized (this) {
  22. this.notifyAll();
  23. System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
  24. }
  25. }

出现死锁

虽然wait会自动解锁,但是对顺序有要求,如果在notify被调用之后,才开始wait方法的调用,线程会永远处于WAITING状态。

  1. public void waitNotifyDeadLockTest() throws Exception {
  2. // 启动线程
  3. new Thread(() -> {
  4. if (resource == null) { // 如果没有可用的临界资源,则进入等待
  5. try {
  6. Thread.sleep(5000L);
  7. } catch (InterruptedException e1) {
  8. e1.printStackTrace();
  9. }
  10. synchronized (this) {
  11. try {
  12. System.out.println(new Date() + " 进入等待............");
  13. this.wait();
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. System.out.println("获取到临界资源,开始运行");
  20. }).start();
  21. // 3秒之后,生产一个临界资源
  22. Thread.sleep(3000L);
  23. System.out.println(new Date());
  24. resource = new Object();
  25. synchronized (this) {
  26. this.notifyAll();
  27. System.out.println(" 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
  28. }
  29. }

3. park / unpark

线程调用park则等待“许可”,unpark方法为指定线程提供“许可(permit)”。
不要求park和unpark方法的调用顺序。
多次调用unpark之后,再调用park,线程会直接运行。但不会叠加,也就是说,连续多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待。

正常执行

  1. public void parkUnparkTest() throws Exception {
  2. // 启动线程
  3. Thread consumerThread = new Thread(() -> {
  4. if (resource == null) { // 如果没有可用的临界资源,则进入等待
  5. System.out.println(new Date() + " 进入等待............");
  6. LockSupport.park();
  7. }
  8. System.out.println("获取到临界资源,开始运行");
  9. });
  10. consumerThread.start();
  11. // 3秒之后,生产一个临界资源
  12. Thread.sleep(3000L);
  13. resource = new Object();
  14. System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
  15. LockSupport.unpark(consumerThread);
  16. }

出现死锁

park并不会释放锁,所有再同步代码中使用可能会出现死锁

  1. public void parkUnparkDeadLockTest() throws Exception {
  2. // 启动线程
  3. Thread consumerThread = new Thread(() -> {
  4. if (resource == null) { // 如果没有可用的临界资源,则进入等待
  5. System.out.println(new Date() + " 进入等待............");
  6. // 当前线程拿到锁,然后挂起
  7. synchronized (this) {
  8. LockSupport.park();
  9. }
  10. }
  11. System.out.println("获取到临界资源,开始运行");
  12. });
  13. consumerThread.start();
  14. // 3秒之后,生产一个临界资源
  15. Thread.sleep(3000L);
  16. System.out.println(new Date());
  17. resource = new Object();
  18. // 争取到锁以后,再恢复consumerThread
  19. synchronized (this) {
  20. LockSupport.unpark(consumerThread);
  21. }
  22. System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
  23. }

三种等待/通知机制的比较

suspend / resumewait / notifypark/unpark
已弃用--
suspend不释放锁wait释放锁park不释放锁
要求suspend / resume执行顺序要求wait / notify执行顺序不要求park/unpark执行顺序

相关文章

最新文章

更多