Java生产者消费者模型

时间:2022-12-11 09:53:59

  在Java中线程同步的经典案例,不同线程对同一个对象同时进行多线程操作,为了保持线程安全,数据结果要是我们期望的结果。

  生产者-消费者模型可以很好的解释这个现象:对于公共数据data,初始值为0,多个线程对其进行增加或者减少,但是我们的目的是无论多少个线程同时操作他,结果都是:当data=0时,只能进行增加,data=1时只能进行减少。

  由于代码比较简单,就把所有的类都写在同一个类里面,以静态内部类的形式出现,这样比较节省篇幅。

  1.线程不安全:

 /**
* 线程不安全
*/
public class NonThreadSafeTest { @Test
public void nonSafeTest() {
Resouce resouce = new Resouce();
Thread add = new Thread(new Run1(resouce), "add");
Thread minus = new Thread(new Run2(resouce), "minus");
add.start();
minus.start();
try {
add.join();
minus.join();
System.err.println("result: " + resouce.data);
} catch (InterruptedException e) {
e.printStackTrace();
}
} static class Run1 implements Runnable {
private Resouce resouce; public Run1(Resouce resouce) {
this.resouce = resouce;
} public void run() {
for (int i = 0; i < 10000; i++)
resouce.add();
}
} static class Run2 implements Runnable {
private Resouce resouce; public Run2(Resouce resouce) {
this.resouce = resouce;
} public void run() {
for (int i = 0; i < 10000; i++)
resouce.minus();
}
} static class Resouce {
public int data = 0; public synchronized void add() {
data++;
} public synchronized void minus() {
data--;
}
}
}

  2.线程安全:

     @Test
public void safeTest() throws Exception {
Resouce resouce = new Resouce();
for (;;) {
new Thread(new Run1(resouce)).start();
new Thread(new Run2(resouce)).start();
}
} static class Run1 implements Runnable {
private Resouce resouce; public Run1(Resouce resouce) {
this.resouce = resouce;
} public void run() {
resouce.add();
}
} static class Run2 implements Runnable {
private Resouce resouce; public Run2(Resouce resouce) {
this.resouce = resouce;
} public void run() {
resouce.minus();
}
} static class Resouce {
public int data = 0; public synchronized void add() {
while (data != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
data++;
System.out.println(data);
notify();
} public synchronized void minus() {
while (data == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
data--;
System.out.println(data);
notify();
}
}
}

  来说明一下其中的信息:

  1.通过JDK源代码我们可以发现:对于new Thread(new Runnable() {@override run() {}}){@override run(){}}.start();这样的形式来说,事实上是new了一个Thread的子类,然后调用start(),同时参数又是实现了Runnable接口的类的实例,二者均重写了run()方法,那么此时如果都重写了run(),就调用new Thread() {}这里面的run方法,而如果仅仅是new Thread(new Runnable() {@override run() {}}).start();这样的形式就会调用参数里面的run方法,见Thread的源码:

    public void run() {
if (target != null) {
target.run();
}
}

调用Thread的run方法,如果此时Thread的run方法没有被重写,并且target不为null,就调用target的run方法,target就是参数传进来的Runnable接口的实现类。

  2.既然上面第一点说到补充些Thread的run方法就调用参数的run方法,所以我们的测试main方法:

    public static void main(String[] args) {
Resouce resouce = new Resouce();
for(;;) {
new Thread(new Run1(resouce)).start();
new Thread(new Run2(resouce)).start();
}
}

就会调用参数(Run1和Run2)的run方法,与此同时,我们分别在Run1和Run2的run方法里面调用Resouce类的add和minus方法,我们让Run1和Run2都持有Reasource这个对象的引用,并且是同一个引用,就实现了多个线程同时操作Resource的同一个实例。然后我们将逻辑(这里就是add和minus这两个同步方法)写在Resource里面。

  3.我们发现在add和minus这两个同步方法里面,add或者minus wait了一段时间被唤醒,他就会执行while块下面的代码,也就是

            data++;
System.out.println(data);
notifyAll();

或者

            data--;
System.out.println(data);
notifyAll();

这两个其中的一个,此时我们使用while来判断被唤醒,使用if也可以,不过这里最好是用while,因为,当线程被唤醒之后,while会再检查while的条件,如果不满足就继续睡眠,而if就直接执行下面的代码,原因是,我隐约的记得以前听张孝祥老师说有正在等待的线程有可能被伪唤醒,如果是被伪唤醒的话,不检查while条件,那么就会出现很严重的问题,所以这里要用while。而JDK的原话解释是这样的:“对于某一个参数的版本,实现中断和虚假唤醒是可能的”,说白了就是有可能不是被notify或者notifyAll唤醒,如果不是被这二者唤醒的,那么是不能让他继续执行的。

前面使用了synchronized+wait/notifyAll的组合,这二者在线程同步上面是一组的,而JDK还提供了另外一组更为灵活强大的线程同步工具, ReentrantLock+Condition,ReentrantLock就相当于synchronized,而Condition就类似与wait/notify,下面给出例子。

public class Resource {

    private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition(); private int num = 0; public void increase() {
try {
lock.lock();
while(num == 1) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.err.println(num + "-Thread ID: " + Thread.currentThread().getId());
condition.signalAll();
} finally {
if(lock.isHeldByCurrentThread()) lock.unlock();
}
} public void decrease() {
try {
lock.lock();
while(num == 0) {
condition.await();
}
num--;
System.err.println(num + "-Thread ID: " + Thread.currentThread().getId());
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(lock.isHeldByCurrentThread()) lock.unlock();
}
} public static void main(String[] args) {
final Resource r = new Resource();
new Thread() {
public void run() {
while(true) r.increase();
};
}.start(); new Thread() {
public void run() {
while(true) r.decrease();
};
}.start();
}
}

其实ReentrantLock的Condition可以做到创建多个条件,每次唤醒通知可以定向唤醒,比如data == 1时候,addCondition等待,唤醒在minusCondition上的等待,而data == 0时,minusCondition等待,唤醒在addCondition上的等待。如下:

 public class LockTest {

     public static void main(String[] args) {
Resource src = new Resource();
List<Thread> ts = new ArrayList<>(20);
for (int i = 0; i < 10; i++) {
Thread add = new Thread(new AddThread(src), "add" + i);
Thread minus = new Thread(new MinusThread(src), "minus" + i);
ts.add(add);
ts.add(minus);
}
for (int i = 0; i < 20; i++) {
Thread t = ts.get(i);
t.start();
}
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
} } class AddThread implements Runnable {
private Resource resource; public AddThread(Resource resource) {
this.resource = resource;
} @Override
public void run() {
for (;;)
resource.add();
} } class MinusThread implements Runnable {
private Resource resource; public MinusThread(Resource resource) {
this.resource = resource;
} @Override
public void run() {
for (;;)
resource.minus();
} } class Resource {
int data = 0;
private ReentrantLock lock = new ReentrantLock();
private Condition addCon = lock.newCondition();
private Condition minusCon = lock.newCondition(); void add() {
try {
lock.lock();
while (data == 1) {
addCon.await();
}
data++;
System.err.println(data + "-----" + Thread.currentThread().getName());
minusCon.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} void minus() {
try {
lock.lock();
while (data == 0) {
minusCon.await();
}
data--;
System.err.println(data + "-----" + Thread.currentThread().getName());
addCon.signal();
} catch (Exception e) {} finally {
lock.unlock();
}
}
}

Java生产者消费者模型的更多相关文章

  1. 第23章 java线程通信——生产者&sol;消费者模型案例

    第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...

  2. Java里的生产者-消费者模型&lpar;Producer and Consumer Pattern in Java&rpar;

    生产者-消费者模型是多线程问题里面的经典问题,也是面试的常见问题.有如下几个常见的实现方法: 1. wait()/notify() 2. lock & condition 3. Blockin ...

  3. Java多线程15:Queue、BlockingQueue以及利用BlockingQueue实现生产者&sol;消费者模型

    Queue是什么 队列,是一种数据结构.除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的.无论使用哪种排序方式,队列的头都是调用remove()或poll()移 ...

  4. 如何在 Java 中正确使用 wait&comma; notify 和 notifyAll – 以生产者消费者模型为例

    wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...

  5. Java多线程之~~~使用Exchanger在线程之间交换数据&lbrack;这个结合多线程并行会有解决很多问题&rsqb;生产者消费者模型

    http://blog.csdn.net/a352193394/article/details/39503857  Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会 ...

  6. Java实现多线程生产者消费者模型及优化方案

    生产者-消费者模型是进程间通信的重要内容之一.其原理十分简单,但自己用语言实现往往会出现很多的问题,下面我们用一系列代码来展现在编码中容易出现的问题以及最优解决方案. /* 单生产者.单消费者生产烤鸭 ...

  7. Java多线程-并发协作&lpar;生产者消费者模型&rpar;

    对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一样,Hello World!都是最经典的例子. 实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓 ...

  8. Java 实现生产者 – 消费者模型

    转自:http://www.importnew.com/27063.html 考查Java的并发编程时,手写“生产者-消费者模型”是一个经典问题.有如下几个考点: 对Java并发模型的理解 对Java ...

  9. 生产者消费者模型Java实现

    生产者消费者模型 生产者消费者模型可以描述为: ①生产者持续生产,直到仓库放满产品,则停止生产进入等待状态:仓库不满后继续生产: ②消费者持续消费,直到仓库空,则停止消费进入等待状态:仓库不空后,继续 ...

随机推荐

  1. Hadoop生态圈

    1.Hadoop是什么? 适合大数据的分布式存储与计算平台 HDFS: Hadoop Distributed File System分布式文件系统 MapReduce:并行计算框架 解决的问题: HD ...

  2. OpenLayers控制瓦片的绽放级别

    先说说这个功能可能使用到的地方,当我们下载的网上瓦片或者矢量数据的第一级或开始几级效果不是很好时,我们就就想让用户看到这些级别的瓦片.实现这个功能比较简单,主要就是修改Openlayers.map的i ...

  3. Params 方法参数

    params,ref,out 方法参数 示例 在下面的方法使用中 OpenWindow(params object[] args) 传递的参数args添加了params修饰 public void O ...

  4. HDU1297 Children’s Queue &lpar;高精度&plus;递推&rpar;

    Children’s Queue Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  5. R语言缺失值信息处理

    mean(!is.na(mat))可以计算数据完整度(没有缺失值的) mean(!is.na(mat))>0.9,90%完整可以使用 # 缺失值的位置研究as.vector(attributes ...

  6. html 页面 ajax 方法显示遮罩

    showLoading.css 样式: ;;list-style-type:none;} a,img{;} .overlay{;;;;;width:100%;height:100%;_padding: ...

  7. Solr使用solr4J操作索引库

    Solrj是Solr搜索服务器的一个比较基础的客户端工具,可以非常方便地与Solr搜索服务器进行交互.最基本的功能就是管理Solr索引,包括添加.更新.删除和查询等.对于一些比较基础的应用,用Solj ...

  8. Android特效专辑&lpar;一&rpar;——水波纹过渡特效&lpar;首页&rpar;

    Android特效专辑(一)--水波纹过渡特效(首页) 也是今天看到的一个特效,感觉挺漂亮的,最近也一直在筹划一个APP,就想把他当做APP的首页,然后加些处理,关于首页APP的特效等我完工了再贴出来 ...

  9. 一个自己研究出来的字符串匹配算法-k子串算法

    前言 最近工作中需要写一个算法,而写完这个算法我却发现了一个很有意思的事情.需要的这个算法是这样的:对于A,B两个字符串,找出最多K个公共子串,使得这K个子串长度和最大.百度之没有这样的算法,然后就开 ...

  10. Linux tee的花式用法和pee

    1.tee多重定向 tee [options] FILE1 FILE2 FILE3... tee的作用是将一份标准输入多重定向,一份重定向到标准输出/dev/stdout,然后还将标准输入重定向到每个 ...