上联:线程、操作(对外暴露的调用方法)、资源类
下联:判断、干活、唤醒通知
横幅:严防多线程并发状态下的虚假唤醒
使用synchronize以及wait()、notify() /notifyAll()
class ShareData {
public static AtomicInteger atomicInteger = new AtomicInteger();
public volatile boolean flag = true;
public static final int MAX_COUNT = 10;
public static final List<Integer> pool = new ArrayList<>();
public void produce() {
// 判断、干活、通知
while (flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
synchronized (pool) {
// 判断
if (pool.size() == MAX_COUNT) {
// 池子满了,生产者停止生产
try {
System.out.println(Thread.currentThread().getName() + "\t pool is full, waiting...");
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 干活
pool.add(atomicInteger.incrementAndGet());
System.out.println(Thread.currentThread().getName() + "\t produce number:" + atomicInteger.get() + "\t" + "current size:" + pool.size());
//通知
pool.notifyAll();
}
}
}
public void consumer() {
// 判断、干活、通知
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
synchronized (pool) {
// 判断
if (pool.size() == 0) {
// 池子空的,停止消费
try {
System.out.println(Thread.currentThread().getName() + "\t pool is empty, waiting...");
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 干活
System.out.println(Thread.currentThread().getName() + "\t consumer number:" + pool.get(0) + "\t" + "current size:" + pool.size());
pool.remove(0);
//通知
pool.notifyAll();
}
}
}
public void stop() {
flag = false;
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
shareData.produce();
}, "AAA").start();
new Thread(() -> {
shareData.consumer();
}, "BBB").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shareData.stop();
}
}
上面的程序在只有两个线程时(一个生产者,一个消费者)可以正常工作。
但是把生产者和消费者线程扩展至多个。就出错了。例如再增加CCC和DDD线程分别生产和消费。只改动了main方法:
public class ProducerConsumerDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
shareData.produce();
}, "AAA").start();
new Thread(() -> {
shareData.consumer();
}, "BBB").start();
new Thread(() -> {
shareData.produce();
}, "CCC").start();
new Thread(() -> {
shareData.consumer();
}, "DDD").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shareData.stop();
}
}
看到 current size 能到11了。这肯定出错了。因为要求pool的最大容量为10。出现这个情况的原因是在多线程的环境下,要防止虚假唤醒。即判断条件不能用 if
,而是用 while
。最终版正确的代码如下:
class ShareData {
public static AtomicInteger atomicInteger = new AtomicInteger();
public volatile boolean flag = true;
public static final int MAX_COUNT = 10;
public static final List<Integer> pool = new ArrayList<>();
public void produce() {
// 判断、干活、通知
while (flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
synchronized (pool) {
// 判断
while (pool.size() == MAX_COUNT) {
// 池子满了,生产者停止生产
try {
System.out.println(Thread.currentThread().getName() + "\t pool is full, waiting...");
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 干活
pool.add(atomicInteger.incrementAndGet());
System.out.println(Thread.currentThread().getName() + "\t produce number:" + atomicInteger.get() + "\t" + "current size:" + pool.size());
//通知
pool.notifyAll();
}
}
}
public void consumer() {
// 判断、干活、通知
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
synchronized (pool) {
// 判断
while (pool.size() == 0) {
// 池子空的,停止消费
try {
System.out.println(Thread.currentThread().getName() + "\t pool is empty, waiting...");
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 干活
System.out.println(Thread.currentThread().getName() + "\t consumer number:" + pool.get(0) + "\t" + "current size:" + pool.size());
pool.remove(0);
//通知
pool.notifyAll();
}
}
}
public void stop() {
flag = false;
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
shareData.produce();
}, "AAA").start();
new Thread(() -> {
shareData.consumer();
}, "BBB").start();
new Thread(() -> {
shareData.produce();
}, "CCC").start();
new Thread(() -> {
shareData.consumer();
}, "DDD").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shareData.stop();
}
}
使用Lock并结合Condition的await和signal方法
JUC
包下的锁Lock
替代synchronize
关键字。await
方法代替wait
,signalAll
代替notifyall
。
class ShareData {
private static AtomicInteger atomicInteger = new AtomicInteger();
private volatile boolean flag = true;
private static final int MAX_COUNT = 10;
private static final List<Integer> pool = new ArrayList<>();
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void produce() {
// 判断、干活、通知
while (flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
lock.lock();
try {
// 判断
while (pool.size() == MAX_COUNT) {
// 池子满了,生产者停止生产
try {
System.out.println(Thread.currentThread().getName() + "\t pool is full, waiting...");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 干活
pool.add(atomicInteger.incrementAndGet());
System.out.println(Thread.currentThread().getName() + "\t produce number:" + atomicInteger.get() + "\t" + "current size:" + pool.size());
//通知
condition.signalAll();
} finally {
lock.unlock();
}
}
}
public void consumer() {
// 判断、干活、通知
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
lock.lock();
try {
// 判断
while (pool.size() == 0) {
// 池子空的,停止消费
try {
System.out.println(Thread.currentThread().getName() + "\t pool is empty, waiting...");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 干活
System.out.println(Thread.currentThread().getName() + "\t consumer number:" + pool.get(0) + "\t" + "current size:" + pool.size());
pool.remove(0);
//通知
condition.signalAll();
} finally {
lock.unlock();
}
}
}
public void stop() {
flag = false;
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
shareData.produce();
}, "AAA").start();
new Thread(() -> {
shareData.consumer();
}, "BBB").start();
new Thread(() -> {
shareData.produce();
}, "CCC").start();
new Thread(() -> {
shareData.consumer();
}, "DDD").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shareData.stop();
}
}
阻塞队列版本
使用阻塞队列根本不需要去加锁,通知,完全通过阻塞队列进行阻塞和通知了。
class ShareData {
private static AtomicInteger atomicInteger = new AtomicInteger();
private volatile boolean flag = true;
// 阻塞队列
private static BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(10);
public void produce() {
while (flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
try {
int incrementAndGet = atomicInteger.incrementAndGet();
boolean value = blockingQueue.offer(incrementAndGet, 2, TimeUnit.SECONDS);
int size = blockingQueue.size();
if (value == true) {
System.out.println(Thread.currentThread().getName() + "\t 成功插入队列 \t" + incrementAndGet + "\t资源队列大小= " + size);
} else {
System.out.println(Thread.currentThread().getName() + "\t 失败插入队列" + incrementAndGet + "\t资源队列大小= " + size);
}
} catch (InterruptedException e) {
}
}
}
public void consumer() {
// 判断、干活、通知
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
try {
Integer result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (null == result) {
System.out.println("超过两秒没有取道数据,消费者即将退出");
return;
}
System.out.println(Thread.currentThread().getName() + "\t 成功消费 \t" + result + "\t" + "资源队列大小= " + blockingQueue.size());
} catch (InterruptedException e) {
}
}
}
public void stop() {
flag = false;
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
shareData.produce();
}, "AAA").start();
new Thread(() -> {
shareData.consumer();
}, "BBB").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shareData.stop();
}
}