Java中生产者消费者问题总结
生产者-消费者算是并发编程中常见的问题。依靠缓冲区我们可以实现生产者与消费者之间的解耦。生产者只管往缓冲区里面放东西,消费者只管往缓冲区里面拿东西。这样我们避免生产者想要交付数据给消费者,但消费者此时还无法接受数据这样的情况发生。
wait notify
这个问题其实就是线程间的通讯,所以要注意的是不能同时读写。生产者在缓冲区满的时候不生产,等待;消费者在缓冲区为空的时候不消费,等待。比较经典的做法是wait和notify。
生产者线程执行15次set操作
public class Producer implements Runnable{ private Channel channel; public Producer(Channel channel) { this.channel = channel; } @Override public void run() { for(int i=0;i<15;i++){ channel.set(Thread.currentThread().getName()+" "+i); } } }
消费者线程执行10次get操作
public class Consumer implements Runnable { private Channel channel; public Consumer(Channel channel) { this.channel = channel; } @Override public void run() { for(int i=0;i<10;i++){ System.out.println("Consumer "+Thread.currentThread().getName()+" get "+channel.get()); } } }
现在定义Channel类,并创建两个生产者线程和三个消费者线程
public class Channel { private List<String> buffer=new ArrayList<>(); private final int MAX_SIZE=10; public synchronized String get(){ while (buffer.size()==0){//不要用if,醒来了也要再次判断 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } String str=buffer.remove(0); notifyAll(); return str; } public synchronized void set(String str){ while (buffer.size()==MAX_SIZE){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } buffer.add(str); notifyAll(); } public static void main(String[] args) { Channel channel=new Channel(); Producer producer=new Producer(channel); Consumer consumer=new Consumer(channel); for(int i=0;i<2;i++){ new Thread(producer).start(); } for (int i=0;i<3;i++){ new Thread(consumer).start(); } } }
使用notifyAll而不是notify的原因是,notify有可能出现多次唤醒同类的情况,造成“假死”。我们可以使用Condition来实现更精确的唤醒。
Condition
将上面代码中的Channel类修改一下即可
public class Channel { private List<String> buffer=new ArrayList<>(); private final int MAX_SIZE=10; private Lock lock=new ReentrantLock(); private Condition producer=lock.newCondition(); private Condition consumer=lock.newCondition(); public String get(){ String str=null; try { lock.lock(); while (buffer.size()==0){ consumer.await(); } str=buffer.remove(0); producer.signalAll(); }catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } return str; } public void set(String str){ try { lock.lock(); while (buffer.size()==MAX_SIZE){ producer.await(); } buffer.add(str); consumer.signalAll(); }catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } }
双缓冲与Exchanger
当同步的花销非常大时,我们可以采用双缓冲区的办法。双缓冲的一个好处就在于:因为生产者和消费者各自拥有一个缓冲区,所以他们不会同时对同一个缓冲区进行操作,那么我们就不需要为读写操作加锁,用空间换了时间。在Java中可以通过Exchanger来交换两个线程之间的数据结构。
public class Producer implements Runnable{ private List<String> buffer; private Exchanger<List<String>> exchanger; public Producer(List<String> buffer, Exchanger<List<String>> exchanger){ this.buffer=buffer; this.exchanger=exchanger; } @Override public void run() { for(int i=0;i<10;i++){ for (int j=0;j<10;j++) buffer.add("Thrad "+Thread.currentThread().getName()+" : "+i+" "+j); try { buffer=exchanger.exchange(buffer); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Consumer implements Runnable { private Exchanger<List<String>> exchanger; private List<String> buffer; public Consumer(List<String> buffer,Exchanger<List<String>> exchanger) { this.exchanger = exchanger; this.buffer = buffer; } @Override public void run() { for(int i=0;i<10;i++){ try { buffer=exchanger.exchange(buffer); } catch (InterruptedException e) { e.printStackTrace(); } for(int j=0;j<10;j++){ String message=buffer.get(0); System.out.println(message); buffer.remove(0); } } } } public class Main { public static void main(String[] args) { List<String> buffer1=new ArrayList<>(); List<String> buffer2=new ArrayList<>(); Exchanger<List<String>> exchanger=new Exchanger<>(); Producer producer=new Producer(buffer1,exchanger); Consumer consumer=new Consumer(buffer2,exchanger); Thread t1=new Thread(producer); Thread t2=new Thread(consumer); t1.start(); t2.start(); } }
BlockingQueue
我们可以使用更为方便安全的阻塞式集合来实现生产消费者模型。
这类集合具有的特点是:当集合已满或者是为空的时候,被调用的方法不会立即执行,该方法将被阻塞,直到可以成功执行为止。
public class Channel { private BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(10); public String get(){ String str=null; try { str=blockingQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } return str; } public void set(String str){ try { blockingQueue.put(str); } catch (InterruptedException e) { e.printStackTrace(); } } }
这次的Channel类是不是比之前的简洁了许多,有了BlockingQueue我们就不用再去写wait和notify了。
到此这篇关于Java中生产者消费者问题总结的文章就介绍到这了,更多相关Java生产者消费者内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!
相关文章
- 这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
java 运行报错has been compiled by a more recent version of the Java Runtime
java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01- 这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
- 这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
- 这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
- 说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
- 这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
- 这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
java 画pdf用itext调整表格宽度、自定义各个列宽的方法
这篇文章主要介绍了java 画pdf用itext调整表格宽度、自定义各个列宽的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-31- 这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
- 这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01
- 这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07
Java使用ScriptEngine动态执行代码(附Java几种动态执行代码比较)
这篇文章主要介绍了Java使用ScriptEngine动态执行代码,并且分享Java几种动态执行代码比较,需要的朋友可以参考下...2021-04-15- 这篇文章主要介绍了Java开发实现人机猜拳游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-03
- 这篇文章主要介绍了Java List集合返回值去掉中括号('[ ]')的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-29
Java 8 Stream 的终极技巧——Collectors 功能与操作方法详解
这篇文章主要介绍了Java 8 Stream Collectors 功能与操作方法,结合实例形式详细分析了Java 8 Stream Collectors 功能、操作方法及相关注意事项,需要的朋友可以参考下...2020-05-20Java中lombok的@Builder注解的解析与简单使用详解
这篇文章主要介绍了Java中lombok的@Builder注解的解析与简单使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-06- 下面小编就为大家带来一篇java中String类型变量的赋值问题介绍。小编觉得挺不错的。现在分享给大家,给大家一个参考。...2016-03-28
- 这篇文章主要介绍了Java线程池中的各个参数如何合理设置操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-19
- 这篇文章主要介绍了详解Java后端优雅验证参数合法性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-18