前言
等待与唤醒机制其实就是经典的生产者与消费者问题
我们知道,生产者要想和消费者作用在一件产品上,必须要用到锁机制。通过锁机制保证一个产品同一时间只有一个角色在执行。
下面将分别使用Synchronized和Lock锁解决生产者和消费者的问题。
1Synchronized版
/**
* 线程之间的通信问题:生产者与消费者问题!
* A和B操作同一个变量,线程交替执行,num=0
* A num+1
* B num-1
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待、业务、通知
class Data{ //数字资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if (number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (number==0){
//进入等待状态
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,-1完毕了
this.notifyAll();
}
}
可以看到线程有序执行没有问题。
2、虚假唤醒
当我们把线程数增加到4个时,2个增操作、2个减操作,代码会报错。
原因是你的增减方法中存在if条件判断,当使用if判断时,只要有一个条件满足,所有被满足的线程都会被唤醒,也就是说一次会唤醒2个+或2个-。解决此问题的办法就是把if判断换成while循环。参考jdk手册
3、Lock版
Lock锁是JUC提供的一个接口。我们在使用时一般用它的实现类ReetrantLock(可重入锁)
在synchronized中我们使用wait和notify进行等待唤醒操作,wait、notify存在于Object类中。而Lock提供的await、signal属于JUC包,Juc为我们提供了condition.await和condition.signal来进行等待与唤醒操作~
/**
* 线程之间的通信问题:生产者与消费者问题!
* A和B操作同一个变量,线程交替执行,num=0
* A num+1
* B num-1
*/
public class B {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待、业务、通知
class Data2{ //数字资源类
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await();等待
//condition.signalAll();唤醒
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
while (number!=0){
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,+1完毕了
condition.signalAll();
} finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0){
//进入等待状态
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,-1完毕了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4、Condition精准通知与唤醒线程
使用Lock锁之后,我们同样实现了等待与唤醒机制,但是ABCD执行是没有顺序的,如何让ABCD顺序执行呢?Condition~
其实原理非常简单,我们设置线程休眠是按顺序唤醒即可A==>B==>C
/**
* A执行完调用B,B执行完调用C,C执行完调用A
*/
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; //1是A,2是B,3是C
public void printA(){
lock.lock();
try {
//判断、执行、通知
while (number !=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"==>AAAA");
number = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
//判断、执行、通知
while (number !=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"==>BBBB");
number = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
//判断、执行、通知
while (number !=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"==>CCCC");
number = 1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5、阻塞队列版
无论是lock还是sychronized都需要我们手动控制等待和唤醒,使用BlockingQueue版解决生产者消费者问题可以达到自动控制等待、唤醒与停止。
class MyResource {
private volatile boolean FLAG = true; //默认开启,进行生产+消费
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProdu() throws Exception {
String data = null;
boolean retValue;
while (FLAG) {
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "失败");
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "\t 大老板叫停了,表示FLAG=false,生产动作结束");
}
public void myConsumer() throws Exception {
String result = null;
while (FLAG) {
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (null == result || result.equalsIgnoreCase("")) {
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t 超过2秒钟没有取到蛋糕,消费退出");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "\t 消费队列蛋糕" + result + "成功");
}
}
public void stop()throws Exception{
this.FLAG = false;
}
}
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) throws Exception {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 生产线程启动");
try {
myResource.myProdu();
} catch (Exception e) {
e.printStackTrace();
}
}, "Prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");
System.out.println();
System.out.println();
try {
myResource.myConsumer();
System.out.println();
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}
}, "Consumer").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println();
System.out.println();
System.out.println("5秒钟时间到,大老板main线程叫停,活动结束");
myResource.stop();
}
}