三种消费者生产者实现模式
在学习多线程的时候 ,写一个消费者生产者模式可能是我们会经常遇到的,今天笔者给大家分享怎么用三种方式实现消费者生产者模式,话不多说直接开干 。
synchronized锁实现
使用synchronized是最古老的一种方式了,但是万变不离其宗,核心都是保证多线程操纵资源类的线程安全,下面直接上代码
。
//资源类
class Resource02 {
private int num = 0;
public synchronized void product() throws InterruptedException {
//在这种多线程环境下一定要使用while,在意外唤醒的情况下也会再次判断
while (num != 0) {
//如果num>0则线程休眠,释放锁资源
this.wait();
}
//睡1秒方便观察
Thread.sleep(1000);
//num自增
System.out.println(Thread.currentThread().getName() + "生产者生产:" + ++num);
//唤醒所有休眠的线程
this.notifyAll();
}
public synchronized void consumer() throws InterruptedException {
while (num == 0) {
this.wait();
}
//睡1秒方便观察
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "消费者消费:" + --num);
this.notifyAll();
}
}
public class SynDemo {
public static void main(String[] args) {
Resource02 r2 = new Resource02();
//生产者和消费者各开5个线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
r2.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
r2.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
结果如下,可以看见消费者生产者各五个,交替执行:
ReentrantLock锁+Condition类实现
这两个类jdk1.5就出现了,都位于JUC包下,可以使用condition类的await()和signal()方法实现精确唤醒,如果现在出去面试,面试官让你写个消费者生产者模式,可不能写synchronized方式的了,主流公司都要求对JUC并发包熟悉,这个题可以直接考验你是否用过JUC中的内容,上代码
。
class Resource{
private int i = 0;
private Lock lock = new ReentrantLock();
//使用condition可以实现精确唤醒,这是和notify、notifyAll的最大区别
private Condition condition = lock.newCondition();
public void increment() {
lock.lock();
try {
while (i!=0){
//生产者等待
condition.await();
}
System.out.println(Thread.currentThread().getName() + "\t 生产" + ++i);
//这里线程睡一秒方便观察
Thread.sleep(1000);
//唤醒消费者
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
while (i==0){
//消费者等待
condition.await();
}
System.out.println(Thread.currentThread().getName() + "\t 消费" + --i);
//这里线程睡一秒方便观察
Thread.sleep(1000);
//唤醒生产者
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
}
public class providerAndConsumer {
public static void main(String[] args) {
Resource resource = new Resource();
//开启两个线程,每个线程执行5次
new Thread(()->{
for (int i = 0; i < 5; i++) {
resource.increment();
}
},"生产者Thread").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
resource.decrement();
}
},"消费者Thread").start();
}
}
这次使用两个线程各执行五次,结果如下:
阻塞队列的方式实现
接下来就是终极版本,今天的主角——阻塞队列版,阻塞队列在很多地方用到,比如线程池,在如今的高并发多线程开发环境下,不会一手阻塞队列,都不好意思说自己会搞java。其实阻塞队列底层也是使用的ReentrantLock类和Condition类来实现的(诸位可以去看下源码)。
上代码
。
class Resource02 {
//这里使用volatile关键字保证条件的可见性
private volatile boolean FLAG = true;
//这里使用原子类来作为共享资源
private AtomicInteger atomicInteger = new AtomicInteger();
//定义一个阻塞队列
private BlockingQueue<String> blockingQueue;
//这边使用接口传值,开发中我们一般都会定义接口,这样拓展性好,尽量避免使用固定的类
public Resource02(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void increment() throws Exception {
String result;
boolean value;
//这里使用循环是为了让生产者线程持续生产
while (FLAG) {
result = atomicInteger.incrementAndGet() + "";
//阻塞队列的offer方法用于往队列中插入生产者生产的资源,这里设置了最大等待时间,2秒如果还不能插入成功就会返回false,
// 成功则返回true
value = blockingQueue.offer(result, 2, TimeUnit.SECONDS);
if (value) {
System.out.println(Thread.currentThread().getName() + "\t插入队列成功" + result);
} else {
System.out.println(Thread.currentThread().getName() + "\t插入队列失败" + result);
}
//沉睡一秒,便于观察
TimeUnit.SECONDS.sleep(1);
}
//调用停止方法后会打印这句话
System.out.println(Thread.currentThread().getName() + "\t停止!FLAG=" + FLAG);
}
public void decrement() throws Exception {
String value = null;
while (FLAG) {
//阻塞队列的poll方法用于往队列中拿出生产者生产的资源,这里设置了最大等待时间,2秒如果还不能拿到值就会返回false,
// 成功则返回true
value = blockingQueue.poll(2, TimeUnit.SECONDS);
if (value == null || value.equalsIgnoreCase("")) {
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t 超过两秒没有取到值,消费者退出。。。");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "\t 消费队列取到值:" + value);
}
}
//定义一个控制停止的方法
public void stop() {
this.FLAG = false;
}
}
public class PACDemo {
public static void main(String[] args) throws InterruptedException {
//传入一个容量为1的数组类型的有界阻塞队列
Resource02 resource02 = new Resource02(new ArrayBlockingQueue<String>(1));
new Thread(() -> {
try {
System.out.println("生产者启动A。");
resource02.increment();
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread_生成者_A").start();
new Thread(() -> {
try {
System.out.println("消费者启动B。。。");
resource02.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread_消费者_B").start();
//5秒后把flag设置为false
TimeUnit.SECONDS.sleep(5);
System.out.println("------------------");
System.out.println("五秒后停止!");
resource02.stop();
}
}
上结果: