多种解法(有些方法属于炫技了,不必太过于追求那些bt的方法),实际上考的是wait、notify、notifyAll

wait、notify、notifyAll和LockSupport的park、unpark也都是作为线程中的一条指令执行的!!!会占据cpu的时间片!

wait、notify、notifyAll+synchronized

  1. 高频面试题,面试重灾区
  2. 使用wait、notify的时候必须要进行锁定,用synchronized锁定,没有synchronized是调不了的
  3. 与LockSupport的park和unpark是非常类似的(华为考的时候是填空题)
  4. 中间的wait换成sleep是不行的,百分百不行,因为sleep是不释放锁的;所以要用wait
  5. 都打印完了要记得notify,因为打印完了终归有一个线程是wait的,是阻塞在这停住不动的,所以不管哪个线程都要notify一下
  1. package com.mashibing.juc.c_026_00_interview.A1B2C3;
  2. public class T06_00_sync_wait_notify {
  3. public static void main(String[] args) {
  4. final Object o = new Object();
  5. char[] aI = "1234567".toCharArray();
  6. char[] aC = "ABCDEFG".toCharArray();
  7. new Thread(()->{
  8. synchronized (o) {
  9. for(char c : aI) {
  10. System.out.print(c);
  11. try {
  12. o.notify();
  13. o.wait(); //让出锁
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. o.notify(); //必须,否则无法停止程序
  19. }
  20. }, "t1").start();
  21. new Thread(()->{
  22. synchronized (o) {
  23. for(char c : aC) {
  24. System.out.print(c);
  25. try {
  26. o.notify();
  27. o.wait();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. o.notify();
  33. }
  34. }, "t2").start();
  35. }
  36. }
  37. //如果我想保证t2在t1之前打印,也就是说保证首先输出的是A而不是1,这个时候该如何做?

  1. 背过
  2. 要保证先打印数字或者字母—->保证先打印的那个线程先运行
    1. 可以用门闩CountDownLatch,第一个线程先await,另外一个线程来了输出第一次之后然后再让他countDown
    2. 用自旋的方式CAS,在t1中使flg变为false;t2中当flg为true时一直自旋—->保证了t1先start
    3. t2上来就wait,t1上来就输出,输出之后就notify
    4. 有好多方法可以实现,类似LockSupport
  3. join是不可行的,调用t2.join表示t2运行结束之后才回来继续运行t1—->与这一题要求两个线程交替运行是不行的

LockSupport

  • 最简单的方法
  • 一个线程输出完了之后停止,让另一个线程继续运行就可以了
  • 互相交替(交替来交替去) ```java package com.mashibing.juc.c_026_00_interview.A1B2C3;

import java.util.concurrent.locks.LockSupport;

//Locksupport park 当前线程阻塞(停止) //unpark(Thread t)

public class T02_00_LockSupport {

  1. static Thread t1 = null, t2 = null;
  2. public static void main(String[] args) throws Exception {
  3. char[] aI = "1234567".toCharArray();
  4. char[] aC = "ABCDEFG".toCharArray();
  5. t1 = new Thread(() -> {
  6. for(char c : aI) {
  7. System.out.print(c);
  8. LockSupport.unpark(t2); //叫醒T2
  9. LockSupport.park(); //T1阻塞
  10. }
  11. }, "t1");
  12. t2 = new Thread(() -> {
  13. for(char c : aC) {
  14. LockSupport.park(); //t2阻塞
  15. System.out.print(c);
  16. LockSupport.unpark(t1); //叫醒t1
  17. }
  18. }, "t2");
  19. t1.start();
  20. t2.start();
  21. }

}

  1. <a name="hDD6B"></a>
  2. ## <br />
  3. <a name="rZ6kh"></a>
  4. ## 能用新的Lock接口(不用synchronized,不用自旋)来实现需求也可以
  5. > 1. Lock接口本质上是和synchronized一样的(底层使用CAS实现的)--->自旋实现
  6. > 1. ReentrantLock+Condition(await和signal)--->synchronized的变种**(不出彩)**
  7. > 1. **写出两个Condition,分了两个等待队列(详见gitlab)--->之前只是一个等待队列**
  8. > 1. **两个等待队列分别对应两个线程(每个等待队列中最多均只有一个线程等待阻塞)**
  9. <br />
  10. <a name="ZYa2G"></a>
  11. ## 自旋式的写法(没用锁自己写了各自旋锁)--->CAS的写法
  12. > 1. 信号灯的意思
  13. > 1. 加volatile**保证线程可见性**,不加volatile起码不能让他马上见到,会浪费cpu时间,让他在那里循环
  14. > 1. 这里有一些更深入的操作--->**lazySet????**
  15. > 1. 用美剧类型主要是为了防止他取别的值,**其实用boolean,用atomic×也行--->用枚举类型这个方法写起来****更严谨一些**
  16. <br />
  17. <a name="G8wGH"></a>
  18. ## BlockingQueue的写法
  19. > 1. 支持多线程的阻塞操作put、take
  20. > 1. 用两个数组实现的BlockingQueue
  21. > 1. 第一个线程打印完了,往第一个队列中放一个元素;此时第二个线程在放入元素之前是阻塞在对第一个队列的take上
  22. > 1. 第二个线程take到值之后,打印;打印结束往第二个队列中放一个元素;此时第一个线程也阻塞在对第二个队列的take上
  23. > 1. CAS无锁、自旋锁(不要咬文嚼字)
  24. > 1. 相当于相互之间举个旗,给对方发一个信号!相互之间牵制
  25. <a name="eJc23"></a>
  26. ## PipedStream(纯炫技)
  27. > 1. 进程之间通信、线程之间通信看成一个管道
  28. > 1. 与一般的管道不一样,是指两个线程之间建立的虚拟管道(用于通信),而不是网络、输入、输出的管道
  29. > 1. 两个线程之间可以相互发送消息
  30. > 1. 效率非常低,里面有各种各样的同步,效率非常低
  31. > 1. 想把两个线程连接起来步骤很多
  32. > 1. 回合制
  33. > 1. 读数据的时候要**用字节数组去读**
  34. > 1. 这里的通道与SocketChannel还是不一样的,Channel是双向的,这里是单向的
  35. > 1. 这里的read和write都是阻塞的,什么时候你写给我了,我就继续(不写给我说明没好,就不继续)
  36. ```java
  37. package com.mashibing.juc.c_026_00_interview.A1B2C3;
  38. import java.io.IOException;
  39. import java.io.PipedInputStream;
  40. import java.io.PipedOutputStream;
  41. public class T10_00_PipedStream {
  42. public static void main(String[] args) throws Exception {
  43. char[] aI = "1234567".toCharArray();
  44. char[] aC = "ABCDEFG".toCharArray();
  45. PipedInputStream input1 = new PipedInputStream();
  46. PipedInputStream input2 = new PipedInputStream();
  47. PipedOutputStream output1 = new PipedOutputStream();
  48. PipedOutputStream output2 = new PipedOutputStream();
  49. input1.connect(output2);
  50. input2.connect(output1);
  51. String msg = "Your Turn";
  52. new Thread(() -> {
  53. byte[] buffer = new byte[9];
  54. try {
  55. for(char c : aI) {
  56. input1.read(buffer);
  57. if(new String(buffer).equals(msg)) {
  58. System.out.print(c);
  59. }
  60. output1.write(msg.getBytes());
  61. }
  62. } catch (IOException e) {
  63. e.printStackTrace();
  64. }
  65. }, "t1").start();
  66. new Thread(() -> {
  67. byte[] buffer = new byte[9];
  68. try {
  69. for(char c : aC) {
  70. System.out.print(c);
  71. output2.write(msg.getBytes());
  72. input2.read(buffer);
  73. if(new String(buffer).equals(msg)) {
  74. continue;
  75. }
  76. }
  77. } catch (IOException e) {
  78. e.printStackTrace();
  79. }
  80. }, "t2").start();
  81. }
  82. }

用信号量Semaphore

  1. 可以同时多少个线程一起运行
  2. 用来限流的,这里的意思是限制一个运行
  3. 但是这里不能限制住哪一个运行,所以不可以(必须用条件变量)
  4. 考的不是并发问题,而是
  5. 控制两个线程之间的指令执行的顺序,所以用一个信号量不行
  6. 可以用两个信号量去做,自己去试一试

用Exchanger来完成(不能实现)

  1. 两个线程交换数据用的
  2. 与上面两个线程之间的通道是类似的
  3. 一个等着另一个交换,交换来交换去,以此达成一个交替进行的顺序(不能实现)
  4. exchange是阻塞的,应该不会有问题(结束的时候有问题???)确实有问题
  5. 多线程中细枝末节的东西比较多!!!要好好琢磨!!!多想想~~~多模拟模拟!!!
  6. 之前确实有问题,因为交换完之后两个打印谁先执行就不一定了

TransferQueue

  1. 生产在那不动了(阻塞),直到另一个线程拿走了,才返回继续运行
  2. 写法的意思是每一个线程都将自己的数字或者字母交到队列中去了,让对方去打印
  3. 互相倒手,就相当于是一个交易
  4. transferQueue一个就可以,这两个线程又是生产者又是消费者
  5. 用transfer传信号也没问题
  6. ❓transfer用一个就可以???

做顺序的时候要进行控制而不能睡,睡代表浪费cpu时间(整个时间)

  1. 这是本来不应该浪费的时间,只要盯着某一个信号即可(即时性
  2. 睡的时间积少成多也是一个很大的开销,除非题目要求,否则不能睡
  3. 睡的时候也能够正确获得顺序,但是不能睡,而是要去控制
  4. sleep是不能写在业务逻辑中的,业务逻辑老是睡觉,搞jb呢???

sleep是不能写在业务逻辑中的