多种解法(有些方法属于炫技了,不必太过于追求那些bt的方法),实际上考的是wait、notify、notifyAll
wait、notify、notifyAll和LockSupport的park、unpark也都是作为线程中的一条指令执行的!!!会占据cpu的时间片!
wait、notify、notifyAll+synchronized
- 高频面试题,面试重灾区
- 使用wait、notify的时候必须要进行锁定,用synchronized锁定,没有synchronized是调不了的
- 与LockSupport的park和unpark是非常类似的(华为考的时候是填空题)
- 中间的wait换成sleep是不行的,百分百不行,因为sleep是不释放锁的;所以要用wait
- 都打印完了要记得notify,因为打印完了终归有一个线程是wait的,是阻塞在这停住不动的,所以不管哪个线程都要notify一下
package com.mashibing.juc.c_026_00_interview.A1B2C3;
public class T06_00_sync_wait_notify {
public static void main(String[] args) {
final Object o = new Object();
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(()->{
synchronized (o) {
for(char c : aI) {
System.out.print(c);
try {
o.notify();
o.wait(); //让出锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify(); //必须,否则无法停止程序
}
}, "t1").start();
new Thread(()->{
synchronized (o) {
for(char c : aC) {
System.out.print(c);
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2").start();
}
}
//如果我想保证t2在t1之前打印,也就是说保证首先输出的是A而不是1,这个时候该如何做?
- 背过
- 要保证先打印数字或者字母—->保证先打印的那个线程先运行
- 可以用门闩CountDownLatch,第一个线程先await,另外一个线程来了输出第一次之后然后再让他countDown
- 用自旋的方式CAS,在t1中使flg变为false;t2中当flg为true时一直自旋—->保证了t1先start
- t2上来就wait,t1上来就输出,输出之后就notify
- 有好多方法可以实现,类似LockSupport
- 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 {
static Thread t1 = null, t2 = null;
public static void main(String[] args) throws Exception {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
t1 = new Thread(() -> {
for(char c : aI) {
System.out.print(c);
LockSupport.unpark(t2); //叫醒T2
LockSupport.park(); //T1阻塞
}
}, "t1");
t2 = new Thread(() -> {
for(char c : aC) {
LockSupport.park(); //t2阻塞
System.out.print(c);
LockSupport.unpark(t1); //叫醒t1
}
}, "t2");
t1.start();
t2.start();
}
}
<a name="hDD6B"></a>
## <br />
<a name="rZ6kh"></a>
## 能用新的Lock接口(不用synchronized,不用自旋)来实现需求也可以
> 1. Lock接口本质上是和synchronized一样的(底层使用CAS实现的)--->自旋实现
> 1. ReentrantLock+Condition(await和signal)--->synchronized的变种**(不出彩)**
> 1. **写出两个Condition,分了两个等待队列(详见gitlab)--->之前只是一个等待队列**
> 1. **两个等待队列分别对应两个线程(每个等待队列中最多均只有一个线程等待阻塞)**
<br />
<a name="ZYa2G"></a>
## 自旋式的写法(没用锁自己写了各自旋锁)--->CAS的写法
> 1. 信号灯的意思
> 1. 加volatile**保证线程可见性**,不加volatile起码不能让他马上见到,会浪费cpu时间,让他在那里循环
> 1. 这里有一些更深入的操作--->**lazySet????**
> 1. 用美剧类型主要是为了防止他取别的值,**其实用boolean,用atomic×也行--->用枚举类型这个方法写起来****更严谨一些**
<br />
<a name="G8wGH"></a>
## BlockingQueue的写法
> 1. 支持多线程的阻塞操作put、take
> 1. 用两个数组实现的BlockingQueue
> 1. 第一个线程打印完了,往第一个队列中放一个元素;此时第二个线程在放入元素之前是阻塞在对第一个队列的take上
> 1. 第二个线程take到值之后,打印;打印结束往第二个队列中放一个元素;此时第一个线程也阻塞在对第二个队列的take上
> 1. CAS无锁、自旋锁(不要咬文嚼字)
> 1. 相当于相互之间举个旗,给对方发一个信号!相互之间牵制
<a name="eJc23"></a>
## PipedStream(纯炫技)
> 1. 进程之间通信、线程之间通信看成一个管道
> 1. 与一般的管道不一样,是指两个线程之间建立的虚拟管道(用于通信),而不是网络、输入、输出的管道
> 1. 两个线程之间可以相互发送消息
> 1. 效率非常低,里面有各种各样的同步,效率非常低
> 1. 想把两个线程连接起来步骤很多
> 1. 回合制
> 1. 读数据的时候要**用字节数组去读**
> 1. 这里的通道与SocketChannel还是不一样的,Channel是双向的,这里是单向的
> 1. 这里的read和write都是阻塞的,什么时候你写给我了,我就继续(不写给我说明没好,就不继续)
```java
package com.mashibing.juc.c_026_00_interview.A1B2C3;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class T10_00_PipedStream {
public static void main(String[] args) throws Exception {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
PipedInputStream input1 = new PipedInputStream();
PipedInputStream input2 = new PipedInputStream();
PipedOutputStream output1 = new PipedOutputStream();
PipedOutputStream output2 = new PipedOutputStream();
input1.connect(output2);
input2.connect(output1);
String msg = "Your Turn";
new Thread(() -> {
byte[] buffer = new byte[9];
try {
for(char c : aI) {
input1.read(buffer);
if(new String(buffer).equals(msg)) {
System.out.print(c);
}
output1.write(msg.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
byte[] buffer = new byte[9];
try {
for(char c : aC) {
System.out.print(c);
output2.write(msg.getBytes());
input2.read(buffer);
if(new String(buffer).equals(msg)) {
continue;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}, "t2").start();
}
}
用信号量Semaphore
- 可以同时多少个线程一起运行
- 用来限流的,这里的意思是限制一个运行
- 但是这里不能限制住哪一个运行,所以不可以(必须用条件变量)
- 考的不是并发问题,而是
- 控制两个线程之间的指令执行的顺序,所以用一个信号量不行
- 可以用两个信号量去做,自己去试一试
用Exchanger来完成(不能实现)
- 两个线程交换数据用的
- 与上面两个线程之间的通道是类似的
- 一个等着另一个交换,交换来交换去,以此达成一个交替进行的顺序(不能实现)
- exchange是阻塞的,应该不会有问题(结束的时候有问题???)确实有问题
- 多线程中细枝末节的东西比较多!!!要好好琢磨!!!多想想~~~多模拟模拟!!!
- 之前确实有问题,因为交换完之后两个打印谁先执行就不一定了
TransferQueue
- 生产在那不动了(阻塞),直到另一个线程拿走了,才返回继续运行
- 写法的意思是每一个线程都将自己的数字或者字母交到队列中去了,让对方去打印
- 互相倒手,就相当于是一个交易
- transferQueue一个就可以,这两个线程又是生产者又是消费者
- 用transfer传信号也没问题
- ❓transfer用一个就可以???
做顺序的时候要进行控制而不能睡,睡代表浪费cpu时间(整个时间)
- 这是本来不应该浪费的时间,只要盯着某一个信号即可(即时性)
- 睡的时间积少成多也是一个很大的开销,除非题目要求,否则不能睡
- 睡的时候也能够正确获得顺序,但是不能睡,而是要去控制
- sleep是不能写在业务逻辑中的,业务逻辑老是睡觉,搞jb呢???
sleep是不能写在业务逻辑中的