在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线程通信——生产者/消费者模型案例

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

  2. Java里的生产者-消费者模型(Producer and Consumer Pattern in Java)

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

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

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

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

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

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

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

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

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

  7. Java多线程-并发协作(生产者消费者模型)

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

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

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

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

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

随机推荐

  1. DNSPod各个套餐的DNS地址

    DNSPod各个套餐的DNS地址 https://support.dnspod.cn/Kb/showarticle/tsid/178/ DNSPod各个套餐的DNS地址每种域名套餐对应的DNS地址是不 ...

  2. 九个uname命令获取Linux系统详情的实例

    当你在控制台模式下,无法通过“鼠标右键 > 关于”获取操作系统的信息.这时,在Linux下,你可以使用uname命令,帮助你完成这些工作. Uname是unix name的缩写.在控制台中实际使 ...

  3. android UI进阶之用ViewPager实现欢迎引导页面

    ViewPager需要android-support-v4.jar这个包的支持,来自google提供的一个附加包.大家搜下即可. ViewPager主要用来组织一组数据,并且通过左右滑动的方式来展示. ...

  4. 获取启动画面图片的string

    支持 iPhone 以下. 支持 iPhone 及 iPad +(NSString*)getLaunchImageName { NSArray* images= @[@"LaunchImag ...

  5. js DOM 元素ID就是全局变量

    有人在twitter上提到了:在Chrome的JavaScript终端中,你只需要输入一个元素的ID,就可以访问到这个元素.@johnjbarton给了解释,这是因为所有的元素ID都是全局变量.本文再 ...

  6. jsp导出Excel功能的实现

    借助POI的excel接口,可以方便得实现excel导出功能: 首先需要引入poi对应的jar包 1.前端jsp页面需要一个a链接. web页面文件MIM类型的下载,只需要一个a元素,该a可以链到该文 ...

  7. 解决多线程调用sql存储过程问题

    场景: 我们程序现在改成多线程了,我现在需要把临时表中的数据给插入到TABLE_M中,但这时候可能其他的线程也在插入,我就不能用之前我们的方案了(select max(oid) from Tuning ...

  8. Swift实战-豆瓣电台(三)获取网络数据

    观看地址:http://v.youku.com/v_show/id_XNzMwMzQxMzky.html 这节内容,我们先说了怎么将storyboard中的组件在类中进行绑定.然后写了一个类用来获取网 ...

  9. Leetcode: Word Pattern II

    Given a pattern and a string str, find if str follows the same pattern. Here follow means a full mat ...

  10. Lintcode: Recover Rotated Sorted Array

    Given a rotated sorted array, recover it to sorted array in-place. Example [4, 5, 1, 2, 3] -> [1, ...