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实现
生产者消费者模型 生产者消费者模型可以描述为: ①生产者持续生产,直到仓库放满产品,则停止生产进入等待状态:仓库不满后继续生产: ②消费者持续消费,直到仓库空,则停止消费进入等待状态:仓库不空后,继续 ...
随机推荐
- EF Code First教程-03 数据库迁移Migrator
要在nuget 程序包管理控制台中输入命令 基本命令 Enable-Migrations //打开数据库迁移 Add-Migration AddBlogUrl //新增一个数据库迁移版本 ...
- Java遇见HTML——JSP篇之商品浏览记录的实现
一.项目总体介绍 使用Cookie实现商品浏览记录. 要实现这个程序采取的是Model1(Jsp+JavaBean)架构实现,具体步骤: 首先要有个数据库,商品表,操作数据库的一个类DBHelper类 ...
- python复杂网络分析库NetworkX
NetworkX是一个用Python语言开发的图论与复杂网络建模工具,内置了常用的图与复杂网络分析算法,可以方便的进行复杂网络数据分析.仿真建模等工作.networkx支持创建简单无向图.有向图和多重 ...
- 设计模式之 -- 单例模式(Singleton)
单例模式是一种常用的软件设计模式,通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问. 使用说明 1.使用时机 在某些系统中某些对象最多只能存在一个,例如Windows中只能打开一个 ...
- SSH 25 tips
port ssh -Nf -L 9800:192.168.1.132:22 -l root 10.100.125.76 -v ssh -l root 127.0.0.1 -p 9800 scp -P ...
- c# Start/Stop/Check Status远程计算机的Windows Service
static void Main(string[] args) { ConnectionOptions op = new ConnectionOptions(); // 登陆远程计算机的远程, op. ...
- jquery 操作select
jQuery("#select_id").change(function(){}); // 1.为Select添加事件,当选择其中一项时触发 var checkValue = jQ ...
- 如何用Java解析CSV文件
首先看一下csv文件的规则: csv(Comma Separate Values)文件即逗号分隔符文件,它是一种文本文件,可以直接以文本打开,以逗号分隔.windows默认用excel打开.它的格式包 ...
- codevs 1201 最小数和最大数
http://codevs.cn/problem/1201/ 1201 最小数和最大数 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 青铜 Bronze 题解 题 ...
- struts_20_对Action中所有方法、某一个方法进行输入校验(基于XML配置方式实现输入校验)
第01步:导包 第02步:配置web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app ...