生产者消费者模型ReetrantLock版本
共 4485字,需浏览 9分钟
·
2021-09-21 19:32
不知道为什么,每次感觉多线程其实没啥难的,但是一旦涉及到细节根本回答不出来,而且也不能很熟练的将多线程的代码编写出来。多线程只是表面理解起来简单,就是有一些任务,本来一个人执行的,现在派大家伙一起来执行他,执行的过程中,大家排好队,记好数,该你动手的时候再动,不该你动手的时候不要动。不管什么悲观锁,乐观锁的,最后都是要按顺序走的。唯一的不同就是一个是提前排好队,记录好大家的信息,然后一个一个来领任务(这其中可能涉及到操作系统用户态和和心态的切换,这个过程可能会比较费时)。另一个就是一堆人时不时的就上去问问现在能不能领任务,不能领的话,我一会儿再来问问。最终就造就了一种共同的表象,我做完,你再做(其实就是线程同步这个概念)。
我打算通过不同版本的生产者消费者模型来搞懂这里面的弯弯绕绕,这样一来,我们才能不至于一直去死记硬背这种本来就很灵活的东西。我们需要实现的功能如下:
有一家面包店,里面有多个烘培师,做完蛋糕放到一个大篮子里,然后外边有一堆客人,需要吃了就去篮子里面取。整个过程要求,面包数量达到篮子容量就停止生产。篮子里面没有面包了就停止消费。同时生产的面包都有各自得编号,一个面包只能被一个人生产,被一个人消费。
今天我们先从ReetrantLock入手,一开始我将生产者和消费者公用一把锁,因为大家都在操作同一个List变量,但是我感觉消费者消费东西的快慢本来不应该去干扰生产者生产东西的快慢。所以我最后选择生产者自己使用一把锁来同步生产者线程,消费者使用一把锁来同步消费者线程。这里面的问题就是客户每次使用size()方法得到的容量实际上不是真正的容量,但是好在不会影响到总数,因为size只有大于1才能进行操作。
烘焙师(生产者)
@Slf4j
public class Baker implements Runnable {
//烘焙师姓名
private String producerName;
//放面包的篮子
private volatile BreadBucket bucket = null;
//保证烘焙师生产顺序的锁
private static ReentrantLock bakerLock = new ReentrantLock();
public Baker(String producerName, BreadBucket bucket){
this.producerName = producerName;
this.bucket = bucket;
}
/**
* 生产方法
*/
private void produce(){
try{
bakerLock.lock();
if(bucket.container.size()<BreadBucket.capacity){
System.out.println(producerName+"生产面包"+ ++BreadBucket.thingsNum);
Thread.sleep(100);//生产面包所需时间0.1s
Bread bread = new Bread("面包"+BreadBucket.thingsNum);
bucket.putInto(bread);
}
}catch (Exception e){
e.printStackTrace();
}finally {
bakerLock.unlock();
}
}
@Override
public void run() {
while (true){
produce();
}
}
}
客户(消费者)
public class Client implements Runnable {
//消费者名称
private String name;
//取面包的篮子
private volatile BreadBucket breadBucket;
//消费者锁
private static ReentrantLock clientLock = new ReentrantLock();
public Client(BreadBucket breadBucket, String name){
this.name = name;
this.breadBucket = breadBucket;
}
/**
* 拿出面包
* @return
*/
private Bread takeOut(){
Bread bread = null;
try{
clientLock.lock();
if(breadBucket.container.size()>=1){//此处遇到问题了
bread = breadBucket.takeOut();
Thread.sleep(200);//消费一个面包0.2秒钟
System.out.println(name+"消费"+bread.getName()
+ "--剩余:"+breadBucket.getAllThing());
}
}catch (Exception e){
e.printStackTrace();
}finally {
clientLock.unlock();
}
return bread;
}
@Override
public void run() {
while (true){
takeOut();
}
}
}
烘焙师放面包和客户取面包的篮子
/**
* 面包桶
* @author Ted
* @version 1.0
* @date 2020/5/30 21:27
*/
public class BreadBucket {
public static Integer thingsNum = 0;
public List<Bread> container = new ArrayList<>();
public static final Integer capacity = 7;
public void putInto(Bread bread){
container.add(bread);
}
public Bread takeOut(){
Random random = new Random();
Bread t = container.get(random.nextInt()&(container.size()-1));
container.remove(t);
return t;
}
public List<Bread> getAllThing(){
return container;
}
}
面包店(main方法)
/**
* @author Ted
* @version 1.0
* @date 2020/5/31 9:47
*/
public class Bakery {
public static void main(String[] args) {
BreadBucket breadBucket = new BreadBucket();
Baker producer1 = new Baker("烘培师1号", breadBucket);
Baker producer2 = new Baker("烘培师2号", breadBucket);
Baker producer3 = new Baker("烘培师3号",breadBucket);
Client consumer1 = new Client(breadBucket,"客户1号");
Client consumer2 = new Client(breadBucket,"客户2号");
Client consumer3 = new Client(breadBucket,"客户3号");
new Thread(consumer1).start();
new Thread(consumer2).start();
new Thread(consumer3).start();
new Thread(producer1).start();
new Thread(producer2).start();
new Thread(producer3).start();
}
}
结果如下
小插曲
本来打算多做几个版本,结果实际上这里面碰到个挺多的小问题。此处先标记一下volatile的问题,当不加这个关键字的时候,如果去掉消费者中的锁,会导致消费者线程被系统挂起,然后生产者生产完第一批产品后,整个程序静置在那里。但是给桶加上volatile关键字之后,去掉消费者锁,程序会一直执行下去,当然去掉锁之后,程序会疯狂报错。如果不加volatile关键字,但是给消费者执行体中加上一句System.out.println语句,会导致消费者线程不会被挂起,会一直执行下去。这个总感觉和JVM优化有一定的联系,导致了线程之间可见性的问题。一点一点搞懂吧!
加上System.out.println,会导致程序起码会运行起来,不会有种死循环的感觉。记录下这个小问题。