面试手写生产者和消费者(四种写法Synchronized、SynchronizedQueue、lock+Condition、BlockingQueue)

生产者消费者方式
生产者生产产品,消费者消费,
有产品可以消费,无则不可以要生产,
生产一个消费一个。

最好烂熟于心,能手写能讲原理,因为面试手写概率比较大,了解第三种将是面试的王牌,毕竟它涉及到了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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值