用门闩CountDownLatch

  1. 用门闩
  2. t2如果不等于5,就latch.await等着
  3. t1中当size等于5是latch.countDown
  4. 门闩会不会被击穿???
  1. /**
  2. * 曾经的面试题:(淘宝?)
  3. * 实现一个容器,提供两个方法,add,size
  4. * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
  5. *
  6. * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
  7. *
  8. * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
  9. * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
  10. *
  11. * 阅读下面的程序,并分析输出结果
  12. * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
  13. * 想想这是为什么?
  14. *
  15. * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
  16. * 整个通信过程比较繁琐
  17. *
  18. * 使用Latch(门闩)替代wait notify来进行通知
  19. * 好处是通信方式简单,同时也可以指定等待时间
  20. * 使用await和countdown方法替代wait和notify
  21. * CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
  22. * 当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了
  23. * 这时应该考虑countdownlatch/cyclicbarrier/semaphore
  24. * @author mashibing
  25. */
  26. package com.mashibing.juc.c_020_01_Interview;
  27. import java.util.ArrayList;
  28. import java.util.List;
  29. import java.util.concurrent.CountDownLatch;
  30. import java.util.concurrent.TimeUnit;
  31. public class T05_CountDownLatch {
  32. // 添加volatile,使t2能够得到通知
  33. volatile List lists = new ArrayList();
  34. public void add(Object o) {
  35. lists.add(o);
  36. }
  37. public int size() {
  38. return lists.size();
  39. }
  40. public static void main(String[] args) {
  41. T05_CountDownLatch c = new T05_CountDownLatch();
  42. CountDownLatch latch = new CountDownLatch(1);
  43. new Thread(() -> {
  44. System.out.println("t2启动");
  45. if (c.size() != 5) {
  46. try {
  47. latch.await();
  48. //也可以指定等待时间
  49. //latch.await(5000, TimeUnit.MILLISECONDS);
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. System.out.println("t2 结束");
  55. }, "t2").start();
  56. try {
  57. TimeUnit.SECONDS.sleep(1);
  58. } catch (InterruptedException e1) {
  59. e1.printStackTrace();
  60. }
  61. new Thread(() -> {
  62. System.out.println("t1启动");
  63. for (int i = 0; i < 10; i++) {
  64. c.add(new Object());
  65. System.out.println("add " + i);
  66. if (c.size() == 5) {
  67. // 打开门闩,让t2得以执行
  68. latch.countDown();
  69. }
  70. /*try {
  71. TimeUnit.SECONDS.sleep(1);
  72. } catch (InterruptedException e) {
  73. e.printStackTrace();
  74. }*/
  75. }
  76. }, "t1").start();
  77. }
  78. }

上述程序存在的问题

  1. 当去掉t1中的sleep之后,会出错
  2. 线程运行的太快了—->打印太快了,该加锁,让t2在正确的位置输出
  3. 确实打开了,但是t1会继续向下运行,等t2打印出来就到后面了
  4. 正确做法:用两只门闩
    1. 一只最开始就拴住t2,在t1中等于5时打开
    2. 一只在等于5时拴住t1,在t2要结束的时候打开
  5. 用两个门闩就可以正确完成这个功能了—->和两对wait、notify是一个道理
  6. t1中sleep的作用是留时间给t2唤醒并输出

用简单方法LockSupport(最方便)

  1. 在t2中不等于5时park,在t1中等于5时unpark
  2. 最方便的写法—->本质和wait、notify差不多
  1. /**
  2. * 曾经的面试题:(淘宝?)
  3. * 实现一个容器,提供两个方法,add,size
  4. * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
  5. *
  6. * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
  7. *
  8. * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
  9. * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
  10. *
  11. * 阅读下面的程序,并分析输出结果
  12. * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
  13. * 想想这是为什么?
  14. *
  15. * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
  16. * 整个通信过程比较繁琐
  17. *
  18. * 使用Latch(门闩)替代wait notify来进行通知
  19. * 好处是通信方式简单,同时也可以指定等待时间
  20. * 使用await和countdown方法替代wait和notify
  21. * CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
  22. * 当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了
  23. * 这时应该考虑countdownlatch/cyclicbarrier/semaphore
  24. * @author mashibing
  25. */
  26. package com.mashibing.juc.c_020_01_Interview;
  27. import java.util.ArrayList;
  28. import java.util.List;
  29. import java.util.concurrent.CountDownLatch;
  30. import java.util.concurrent.TimeUnit;
  31. import java.util.concurrent.locks.LockSupport;
  32. //TODO park unpark
  33. public class T06_LockSupport {
  34. // 添加volatile,使t2能够得到通知
  35. volatile List lists = new ArrayList();
  36. public void add(Object o) {
  37. lists.add(o);
  38. }
  39. public int size() {
  40. return lists.size();
  41. }
  42. public static void main(String[] args) {
  43. T06_LockSupport c = new T06_LockSupport();
  44. CountDownLatch latch = new CountDownLatch(1);
  45. Thread t2 = new Thread(() -> {
  46. System.out.println("t2启动");
  47. if (c.size() != 5) {
  48. LockSupport.park();
  49. }
  50. System.out.println("t2 结束");
  51. }, "t2");
  52. t2.start();
  53. try {
  54. TimeUnit.SECONDS.sleep(1);
  55. } catch (InterruptedException e1) {
  56. e1.printStackTrace();
  57. }
  58. new Thread(() -> {
  59. System.out.println("t1启动");
  60. for (int i = 0; i < 10; i++) {
  61. c.add(new Object());
  62. System.out.println("add " + i);
  63. if (c.size() == 5) {
  64. LockSupport.unpark(t2);
  65. }
  66. /*try {
  67. TimeUnit.SECONDS.sleep(1);
  68. } catch (InterruptedException e) {
  69. e.printStackTrace();
  70. }*/
  71. }
  72. }, "t1").start();
  73. }
  74. }

上述程序存在和单个门闩相同的问题

  1. 解决方案:用两个LockSupport
  2. 在t1等于5时,先调用unpark解除t2的阻塞,再调用park将自己阻塞,等待t2线程的执行输出
  3. 当t2执行结束,t2调用unpark将t1的阻塞状态解除,让t1继续向下执行
  4. 必须保证t2先执行
  5. 本质上和wait、notify差不多!!!
  6. 不会出现t2来不及运行,t1直接运行完的情况,因为t1会在等于5是park自己
  7. t2中的等于5的判断可以不用,可以一上来就将自己park
  8. 线程t1中unpark(t2)在park之前是可以的,因为unpark(t2)只能解除t2的阻塞而不能解除t1的阻塞,unpark是有针对性的
  9. t1不park时,可能会出现当t1已经输出了6、7个元素的时候,t2才会得到继续向下运行的机会—->是一个抢占cpu时间片的过程(线程调度器、线程调度算法!!!)—->可能会出现即使将t2唤醒了,t2也得不到执行的机会,而t1执行得非常快,造成t1继续向下打印输出而t2要过会才能输出!!!(要过会才能得到输出的机会)
  10. 多线程的程序得多探讨!!!
  1. /**
  2. * 曾经的面试题:(淘宝?)
  3. * 实现一个容器,提供两个方法,add,size
  4. * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
  5. *
  6. * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
  7. *
  8. * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
  9. * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
  10. *
  11. * 阅读下面的程序,并分析输出结果
  12. * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
  13. * 想想这是为什么?
  14. *
  15. * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
  16. * 整个通信过程比较繁琐
  17. *
  18. * 使用Latch(门闩)替代wait notify来进行通知
  19. * 好处是通信方式简单,同时也可以指定等待时间
  20. * 使用await和countdown方法替代wait和notify
  21. * CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
  22. * 当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了
  23. * 这时应该考虑countdownlatch/cyclicbarrier/semaphore
  24. * @author mashibing
  25. */
  26. package com.mashibing.juc.c_020_01_Interview;
  27. import java.util.ArrayList;
  28. import java.util.List;
  29. import java.util.concurrent.CountDownLatch;
  30. import java.util.concurrent.TimeUnit;
  31. import java.util.concurrent.locks.LockSupport;
  32. //TODO park unpark
  33. public class T07_LockSupport_WithoutSleep {
  34. // 添加volatile,使t2能够得到通知
  35. volatile List lists = new ArrayList();
  36. public void add(Object o) {
  37. lists.add(o);
  38. }
  39. public int size() {
  40. return lists.size();
  41. }
  42. static Thread t1 = null, t2 = null;
  43. public static void main(String[] args) {
  44. T07_LockSupport_WithoutSleep c = new T07_LockSupport_WithoutSleep();
  45. t1 = new Thread(() -> {
  46. System.out.println("t1启动");
  47. for (int i = 0; i < 10; i++) {
  48. c.add(new Object());
  49. System.out.println("add " + i);
  50. if (c.size() == 5) {
  51. LockSupport.unpark(t2);
  52. LockSupport.park();
  53. }
  54. }
  55. }, "t1");
  56. t2 = new Thread(() -> {
  57. //System.out.println("t2启动");
  58. //if (c.size() != 5) {
  59. LockSupport.park();
  60. //}
  61. System.out.println("t2 结束");
  62. LockSupport.unpark(t1);
  63. }, "t2");
  64. t2.start();
  65. t1.start();
  66. }
  67. }

🤏随想

  1. java可以先给变量进行声明,声明了之后再进行赋值;这样就不存在两个变量循环依赖但另一个变量没有定义的情况了!!!

image.png

  1. lambda表达式中必须是final或者effectively final的

image.png