生产者消费者方式
生产者生产产品,消费者消费,
有产品可以消费,无则不可以要生产,
生产一个消费一个。
最好烂熟于心,能手写能讲原理,因为面试手写概率比较大,了解第三种将是面试的王牌,毕竟它涉及到了CAS,volite和BlockQueue的知识,将提高的价值!
方式一:最经典的Synchronized版本
package com.ProCon;
public class ProConTest1 {
//这一种只需要在生产和消费方法加上sychronized锁就好了,然后生产完即通知消费即可,如果num为0则消费停止然后生产,num=1则反之
public static void main(String[] args) {
Data1 data = new Data1();
new Thread(() -> {
for (int i = 0; i < 5; i++)
try {
data.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "水果店").start();
new Thread(() -> {
for (int j = 0; j < 5; j++)
try {
data.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "顾客").start();
}
}
class Data1 {
private int num = 0;
public synchronized void product() throws InterruptedException {
while (num != 0) {// 不用if防止虚假唤醒,官方API文档建议用while,if用了会出错!
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "卖出:" + num + "个苹果");
this.notify();
}
public synchronized void consumer() throws InterruptedException {
while (num == 0) {// 不用if防止虚假唤醒,官方API文档建议用while,if用了会出错!
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "买了:" + num + "个苹果");
this.notify();
}
}
执行结果:
方式二:用同步阻塞队列SynchronizedQueue
package com.zhang;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class BlockingQueueTest {
public static void main(String[] args) {
//SynchronousQueue不存在容量的说法,即只存一个元素,任何插入操作都需要等待其他线程来消费,否则就会阻塞等待
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "\t ---> 生产 put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "\t ---> 生产 put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "\t ---> 生产 put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"生产者").start();
new Thread(()->{
try {
//这里为了掩饰效果,设置等待3秒,可以不用写等待3秒
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t ---> 消费"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t ---> 消费"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t ---> 消费"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"消费者").start();
}
}
执行结果:
方式三:用lock类的lock+Condition
代码样例1
package com.ProCon;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*线程 操作方法 资源类
* 判断 干活 通知
* 防止虚假唤醒
*/
class ShareData {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//生产者
public void increment() throws Exception{
lock.lock();
//判断
while(number!= 0) {
//等待,不能生产
condition.await();
}
//干活
number ++;
System.out.println("线程"+Thread.currentThread().getName()+"\t生产了 "+number+" 苹果");
//唤醒
condition.signalAll();
lock.unlock();
}
//消费者
public void decrement() throws Exception {
lock.lock();
while(number == 0) {
condition.await();
}
System.out.println("线程"+Thread.currentThread().getName()+"\t消费了 "+number+" 个苹果");
number --;
condition.signalAll();
lock.unlock();
}
}
public class ProConTest2Plus {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(()->{
for(int i= 0;i<5;i++) {
try {
shareData.increment();
} catch (Exception e) {
// TODO: handle exception
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<5;i++) {
try {
shareData.decrement();
} catch (Exception e) {
// TODO: handle exception
}
}
},"B").start();
}
}
代码样例2
package com.ProCon;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProConTest2 {
//一样的加锁,但不是对于方法加锁,而是在生产核心和消费核心加锁
//可以选择唤醒的线程,比如这里是有个有钱的客人来消费,水果1通知水果店2一起卖水果,可以选择通知具体线程唤醒
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
data.product1();
}
}, "水果店1").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
data.product2();
}
}, "水果店2").start();
new Thread(() -> {
for (int j = 0; j < 5; j++) {
data.consumer();
}
}, "顾客").start();
}
}
class Data2 {
private int num = 0;
Lock lock = new ReentrantLock();// 默认可重入的非公平锁
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
public void product1() {
lock.lock();
try {
while (num != 0) {// 不用if防止虚假唤醒
c1.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "卖出了" + num + "个苹果");
c2.signal();//Condition下的signal方法是唤醒
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void product2() {
lock.lock();
try {
while (num != 1) {// 不用if防止虚假唤醒
c2.await();
}
System.out.println(Thread.currentThread().getName() + "卖出了" + num + "个苹果");
num++;
c3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consumer() {
lock.lock();
try {
while (num != 2) {// 不用if防止虚假唤醒
c3.await();
}
System.out.println(Thread.currentThread().getName() + "消费了" + num + "个苹果");
num = 0;
c1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
执行结果:
方式四:利用BlockingQueue解决加锁问题
package com.ProCon;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ProConTest3 {
//用了volite保证了可见性,
//BlockingQueue保证了原子性,共享资源的问题
//这里用一个促销活动为例子,促销卖苹果,6秒后main主动叫停
public static void main(String[] args) {
Data data = new Data(new ArrayBlockingQueue<>(3));
new Thread(() -> {
try {
System.out.println("水果店生产启动");
data.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "水果店").start();
new Thread(() -> {
System.out.println("顾客开始消费");
try {
data.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "顾客").start();
try {
TimeUnit.SECONDS.sleep(6);
data.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("6秒后活动结束,水果店老板叫停了");
}
}
class Data {
private volatile boolean num = true;
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
//保证通用性,用Spring的属性注入(不用get\set注入)
Data(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println("传进来的队列类型是" + blockingQueue.getClass().getName());
}
public void product() throws InterruptedException {
String phonenum = "";
boolean flag;
while (num) {// 不用if防止虚假唤醒
phonenum = atomicInteger.incrementAndGet() + "";
flag = blockingQueue.offer(phonenum, 2, TimeUnit.SECONDS);// 2秒offer增加一个
if (flag) {
System.out.println(Thread.currentThread().getName() + "卖出了" + phonenum + "个苹果");
} else {
System.out.println(Thread.currentThread().getName() + "卖出了" + phonenum + "个苹果失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "水果店停止出售苹果");
}
public void consumer() throws InterruptedException {
String result = null;
while (num) {// 不用if防止虚假唤醒
result = blockingQueue.poll(2, TimeUnit.SECONDS);// 2秒poll取出一个
if (null == result || result.equalsIgnoreCase("")) {
num = false;
System.out.println(Thread.currentThread().getName() + "买不到苹果了,决定不消费了");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "消费了" + result + "个苹果");
}
}
public void stop() {
num = false;
}
}
执行结果:
synchronized和lock区别
1、原始构成
synchronized是java关键字,属于JVM层面;
lock是具体类,java.util.concurrent.locks.lock,是API层面
2、使用方法
synchronized不需要用户手动释放锁,执行完成,系统自动让线程释放锁;
reentrantlock需要用户手动释放锁,可能导致出现死锁现象,需要lock、unlock方法配合try\catch语句块来完成
3、等待是否可中断
synchronized不可中断,除非正常完成或者抛出异常;
renntrantlock可中断:
(1)设置超时方法trylock(long timeout,TimeUnit unit)
(2)lockInterruptibly()放代码块中,调用interrupt()方法中断
4、加锁是否公平
synchronized非公平锁;
reentrantlock可公平可不公平,默认是非公平锁,构造方法传入true则为公平锁,传入false则为非公平锁
5、锁绑定多个条件Condition
synchronized没有,要么随机唤醒一个线程,要么唤醒全部线程;
reentrantlock用来实现分组唤醒或精确唤醒
参考文章
https://blog.youkuaiyun.com/qq_26542493/article/details/104507725
https://blog.youkuaiyun.com/jiohfgj/article/details/104864248