『深入学习Java』(五) Java 并发经典题
生产者/消费者
这里模拟了一个消息队列,来说明生产者/消费者问题。
我们需要实现几个特点:
- 消息队列有固定长度。
- 消费者一直消费队列消息,若无消息可消费则暂停。
- 生产者一直生产消息,若队列已满则暂停。
实际上 Java 中的各种阻塞队列,也是采用的这种模式。
- 消息体定义 ```java
@Data
public static class Msg {
private String id;
private String msg;
}
- 消息队列定义
public static class MsgQueue {
private LinkedList<Msg> queue;
private int size;
public MsgQueue(int size) {
this.queue = new LinkedList<>();
this.size = size;
}
public Msg take() {
synchronized (queue) {
// 如果没数据,则等待
while (queue.isEmpty()) {
log.info("队列没数据了。等一会儿再取吧。");
try {
queue.wait();
} catch (InterruptedException e) {..}
}
Msg pop = queue.pop();
log.info("消费队列数据。剩余队列大小:{}", queue.size());
// 添加完数据后,唤醒等待线程。
queue.notifyAll();
return pop;
}
}
public synchronized void put(Msg msg) {
synchronized (queue) {
// 如果队列满了,则进入等待
while (queue.size() >= size) {
log.info("队列数据满了。等一会儿再添加吧。");
try {
queue.wait();
} catch (InterruptedException e) {..}
}
queue.add(msg);
log.info("添加队列数据。队列大小:{}", queue.size());
// 添加完数据后,唤醒等待线程
queue.notifyAll();
}
}
}
执行结果
20:51:04.219 [Thread-5] INFO ThreadLockTest - 消费队列数据。剩余队列大小:2
20:51:04.219 [Thread-5] INFO ThreadLockTest - 消费队列数据。剩余队列大小:1
20:51:04.219 [Thread-5] INFO ThreadLockTest - 消费队列数据。剩余队列大小:0
20:51:04.219 [Thread-5] INFO ThreadLockTest - 队列没数据了。等一会儿再取吧。
20:51:04.219 [Thread-1] INFO ThreadLockTest - 添加队列数据。队列大小:1
20:51:04.219 [Thread-1] INFO ThreadLockTest - 添加队列数据。队列大小:2
20:51:04.219 [Thread-5] INFO ThreadLockTest - 消费队列数据。剩余队列大小:1
20:51:04.219 [Thread-5] INFO ThreadLockTest - 消费队列数据。剩余队列大小:0
20:51:04.219 [Thread-5] INFO ThreadLockTest - 队列没数据了。等一会儿再取吧。
20:51:04.219 [Thread-1] INFO ThreadLockTest - 添加队列数据。队列大小:1
20:51:04.219 [Thread-1] INFO ThreadLockTest - 添加队列数据。队列大小:2
20:51:04.219 [Thread-5] INFO ThreadLockTest - 消费队列数据。剩余队列大小:1
20:51:04.219 [Thread-5] INFO ThreadLockTest - 消费队列数据。剩余队列大小:0
.....
固定顺序
线程1输出a,线程2输出b。要求固定按照ba运行。
final Object o = new Object();
boolean flag = false;
@Test
public void test05() {
Thread thread1 = new Thread(() -> {
synchronized (o) {
// 如果 thread2 还没执行,就等一会儿。
while (!flag) {
try {
o.wait();
} catch (InterruptedException e) {...}
}
System.out.println("a");
}
});
Thread thread2 = new Thread(() -> {
synchronized (o) {
System.out.println("b");
flag = true;
o.notifyAll();
}
});
thread1.start();
thread2.start();
}
给定次序打印
线程1 输出5次a,线程2 输出5次b,线程3 输出5次c。要求输出打印结果 abcabcabcabcabc。
Object.wait/notify 版本
public void test04() {
TurnPrint turnPrint = new TurnPrint("1", 5);
new Thread(() -> turnPrint.print("1","2","a")).start();
new Thread(() -> turnPrint.print("2","3","b")).start();
new Thread(() -> turnPrint.print("3","1","c")).start();
}
public static class TurnPrint {
private int times;
private String flag;
public TurnPrint(String flag,int times) {
this.times = times;
this.flag = flag;
}
public void print(String thisFlag,String nextFlag,String printStr) {
for (int i = 0; i < times; i++) {
synchronized (this) {
while (!Objects.equals(thisFlag,flag)) {
try {
this.wait();
} catch (InterruptedException e) {}
}
System.out.println(printStr);;
flag=nextFlag;
this.notifyAll();
}
}
}
}
运行结果
abcabcabcabcabc
Lcok-Condition 版本
Condition 相对了 wait/notify 的区别在于,我可以知道需要第一个执行的线程在哪个 Conditon 上。
所以我们可以先让所有线程都 wait,然后精准唤醒需要第一个执行的线程。
@Test
public void test06() {
ReentrantLock lock1 = new ReentrantLock();
Condition c1 = lock1.newCondition();
Condition c2 = lock1.newCondition();
Condition c3 = lock1.newCondition();
Test06Print test06Print = new Test06Print(5, c1, lock1);
new Thread(() -> test06Print.print(c1,c2,"a")).start();
new Thread(() -> test06Print.print(c2,c3,"b")).start();
new Thread(() -> test06Print.print(c3,c1,"c")).start();
test06Print.notifyFirst();
}
@AllArgsConstructor
public static class Test06Print {
private int times;
private Condition firstCondition;
private Lock lock;
public void print(Condition thisCondi, Condition nextCondi, String str) {
for (int i = 0; i < times; i++) {
lock.lock();
try {
thisCondi.await();
System.out.println(str);
nextCondi.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
public void notifyFirst() {
lock.lock();
try {
firstCondition.signal();
} finally {
lock.unlock();
}
}
}