Java学习笔记-生产者消费者问题
synchronized
我们可以使用synchronized来解决生产者消费者问题,比如当num为0时,使B线程等待,唤醒A线程让其加1,当num为1时使A线程等待,并唤醒B线程让其减1
public class PCSyn{
public static void main(String[] args){
// 获取资源对象
Data data = new Data();
// 让其执行十次
for(int i = 0; i < 10; i ++){
// A线程执行增加操作
new Thread(()->{data.increment();},"A").start();
// B线程执行减操作
new Thread(()->{data.decrement();},"B").start();
}
}
}
class Data{
// 信号量
private int num = 0;
// 增1
public synchronized void increment(){
// 当num不为0时让该线程等待
if(num != 0)
this.wait();
// 执行业务操作
num ++;
System.out.println(Thread.currentThread().getName() + "->" + num);
// 执行完成,唤醒
notifyAll();
}
public synchronized void decrement(){
// 当num为0时让该线程等待
if(num == 0)
this.wait();
// 执行业务操作
num --;
System.out.println(Thread.currentThread().getName() + "->" + num);
// 执行完成,唤醒
notifyAll();
}
}
到这,在AB两个线程执行时,结果确实是按照我们的预想交替出现01,但是,这样的代码就没有问题了吗?
同样的情况如果我们在上述代码中再加入两个线程CD执行同样的操作,出现的结果会是我们想要的吗?显然,结果并不是
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1cjAxYKg-1622540836997)(C:\Users\10098\AppData\Roaming\Typora\typora-user-images\image-20210601110236722.png)]
那么,为什么会出现这样的情况呢?
我们可以看到,当num因上一次的操作数值发生变化之后,我们进行了if判断,if判断只判断了一次,当这个条件被满足时,比如num此时为0,那么线程A和线程C都被唤醒了,但是其中只有一个线程是有用的,那么另一个就是虚假唤醒,根据官方文档
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eEOJ8qas-1622540837000)(C:\Users\10098\AppData\Roaming\Typora\typora-user-images\image-20210601114128048.png)]
所以,要想解决这个问题,那么只需将我们等待的判断条件处于循环中即可
public class PCSyn{
public static void main(String[] args){
// 获取资源对象
Data data = new Data();
// 让其执行十次
for(int i = 0; i < 10; i ++){
// A线程执行增加操作
new Thread(()->{data.increment();},"A").start();
// B线程执行减操作
new Thread(()->{data.decrement();},"B").start();
}
}
}
class Data{
// 信号量
private int num = 0;
// 增1
public synchronized void increment(){
// 当num不为0时让该线程等待
while(num != 0)
this.wait();
// 执行业务操作
num ++;
System.out.println(Thread.currentThread().getName() + "->" + num);
// 执行完成,唤醒
notifyAll();
}
public synchronized void decrement(){
// 当num为0时让该线程等待
while(num == 0)
this.wait();
// 执行业务操作
num --;
System.out.println(Thread.currentThread().getName() + "->" + num);
// 执行完成,唤醒
notifyAll();
}
}
Lock
同样的问题我们也可以使用Lock接口去实现
public class PCLock{
public static void main(String[] args){
Data data = new Data();
for(int i = 0; i < 10; i ++){
new Thread(()->{data.increment();},"A").start();
new Thread(()->{data.decrement();},"B").start();
new Thread(()->{data.increment();},"C").start();
new Thread(()->{data.decrement();},"D").start();
}
}
}
class Data{
private int num = 0;
private Lock lock = new ReentrantLock();
// 和synchronized不同的是,lock的等待和唤醒在condition对象中,分别对应await()和signalAll()
private Condition condition = lock.newCondition();
public void increment(){
lock.lock();
try{
while(num != 0)
num ++;
System.out.println(Thread.currentThread().getName() + "->" + num);
condition.signalAll();
} catch(Exception e){
e.prinyStackTrace();
} finally{
lock.unlock();
}
}
public void decrement(){
lock.lock();
try{
while(num == 0)
num --;
System.out.println(Thread.currentThread().getName() + "->" + num);
condition.signalAll();
} catch(Exception e){
e.prinyStackTrace();
} finally{
lock.unlock();
}
}
}
但是,明明能用synchronized解决的问题,为什么要引入一个新的技术呢,这个技术肯定有什么不同的、而且比原有的技术优秀的地方。那么在lock的condition中,我们是可以精准的控制是哪个线程被唤醒,哪个线程等待,比如:实现A线程打印A,B线程打印B,C线程打印C,并且让其每一次出现的顺序是A->B->C
public class PrintLock{
public static void main(String[] args){
Data data = new Data();
for(int i = 0; i < 10; i ++){
new Thread(()->{data.printA();},"A").start();
new Thread(()->{data.printB();},"B").start();
new Thread(()->{data.printC();},"C").start();
}
}
}
class Data{
private int num = 1; // 1.A线程 2.B线程 3.C线程
private Lock lock = new ReentrantLock();
// 通过多个同步监视器精准唤醒或等待某个线程
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA(){
lock.lock();
try{
while(num != 1)
conditino1.await();
System.out.println(Thread.currentThread().getName() + "->AAA");
// 唤醒B线程
condition2.signal();
} catch(Exception e){
e.printStackTrace();
} finally{
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
while(num != 2)
conditino1.await();
System.out.println(Thread.currentThread().getName() + "->BBB");
// 唤醒C线程
condition3.signal();
} catch(Exception e){
e.printStackTrace();
} finally{
lock.unlock();
}
}
public void printA(){
lock.lock();
try{
while(num != 3)
conditino1.await();
System.out.println(Thread.currentThread().getName() + "->CCC");
// 唤醒A线程
condition1.signal();
} catch(Exception e){
e.printStackTrace();
} finally{
lock.unlock();
}
}
}