Java生产者消费者模型
在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生产者消费者模型的更多相关文章
- 第23章 java线程通信——生产者/消费者模型案例
第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...
- Java里的生产者-消费者模型(Producer and Consumer Pattern in Java)
生产者-消费者模型是多线程问题里面的经典问题,也是面试的常见问题.有如下几个常见的实现方法: 1. wait()/notify() 2. lock & condition 3. Blockin ...
- Java多线程15:Queue、BlockingQueue以及利用BlockingQueue实现生产者/消费者模型
Queue是什么 队列,是一种数据结构.除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的.无论使用哪种排序方式,队列的头都是调用remove()或poll()移 ...
- 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例
wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...
- Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会有解决很多问题]生产者消费者模型
http://blog.csdn.net/a352193394/article/details/39503857 Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会 ...
- Java实现多线程生产者消费者模型及优化方案
生产者-消费者模型是进程间通信的重要内容之一.其原理十分简单,但自己用语言实现往往会出现很多的问题,下面我们用一系列代码来展现在编码中容易出现的问题以及最优解决方案. /* 单生产者.单消费者生产烤鸭 ...
- Java多线程-并发协作(生产者消费者模型)
对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一样,Hello World!都是最经典的例子. 实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓 ...
- Java 实现生产者 – 消费者模型
转自:http://www.importnew.com/27063.html 考查Java的并发编程时,手写“生产者-消费者模型”是一个经典问题.有如下几个考点: 对Java并发模型的理解 对Java ...
- 生产者消费者模型Java实现
生产者消费者模型 生产者消费者模型可以描述为: ①生产者持续生产,直到仓库放满产品,则停止生产进入等待状态:仓库不满后继续生产: ②消费者持续消费,直到仓库空,则停止消费进入等待状态:仓库不空后,继续 ...
随机推荐
- form表单中控件较多,加载完成后切换页面都很慢的解决方法
form表单中控件较多,加载完成后点击都很慢,为什么?我一页面中form表单里面上百个控件(如input.select.radio.checkbox等),还有一些js脚本,加载速度还可以,都能全部显示 ...
- 第十篇 Replication:故障排除
本篇文章是SQL Server Replication系列的第十篇,详细内容请参考原文. 复制故障排除是一项艰巨的任务.在任何复制设置中,都涉及到很多移动部件,而可用的工具并不总是很容易识别问题.Th ...
- python set add 导致问题 TypeError: unhashable type: 'list'
问题复现 >>> a = set() >>> b = set() >>> b.add(1) >>> a.add(b) Trace ...
- redmine发送邮件
vim /data/server/redmine/apps/redmine/htdocs/config/configuration.yml default: # Outgoing emails con ...
- LinQ系列文章
温故而知新,想着系统再学习一次LinQ知识点,发现园子里有个非常棒的系列文章,所以Mark下来,方便以后查阅! 系列博客导航: LINQ之路系列博客导航 LINQ之路 1:LINQ介绍 LINQ之路 ...
- Android中三种onClick事件的实现与对比
方式一:在activity的onCreate()方法中,嵌入如下代码: Button button = (Button)findViewById(R.id.button1); button.setOn ...
- 转:Jmeter之Bean shell使用(二)
上一篇Jmeter之Bean shell使用(一)简单介绍了下Jmeter中的Bean shell,本文是对上文的一个补充,主要总结下常用的几种场景和方法,相信这些基本可以涵盖大部分的需求.本节内容如 ...
- fzuoj Problem 2182 水题
http://acm.fzu.edu.cn/problem.php?pid=2182 Problem 2182 水题 Accept: 188 Submit: 277Time Limit: 100 ...
- .net 网站预编译命令
aspnet_compiler -v /Aspnet -p "C:\inetpub\wwwroot\a" C:\inetpub\wwwroot\a2 /Aspnet iis ...
- java经典小算法
package com.shb.java; public class Demo4 { /**时间有限 先不写文字了 自己随便敲的 * @param args * @author shaobn */ p ...