第23章 java线程通信——生产者/消费者模型案例

1.案例:

package com.rocco;

/**
* 生产者消费者问题,涉及到几个类
* 第一,这个问题本身就是一个类,即主类
* 第二,既然是生产者、消费者,那么生产者类和消费者类就是必须的
* 第三,生产什么,消费什么,所以物品类是必须的,这里是馒头类
* 第四,既然是线程,那么就不是一对一的,也就是说不是生产一个消费一个,既然这样,多生产的往哪里放
* 现实中就是筐了,在计算机中也就是数据结构,筐在数据结构中最形象的就是栈了,因此还要一个栈类
*/ public class ProduceConsume {
public static void main(String[] args) {
SyncStack ss = new SyncStack();//建造一个装馒头的框
Producer p = new Producer(ss);//新建一个生产者,并把已经创建好框传给它,使其使用这个框
Consume c = new Consume(ss);//新建一个消费者,并把已经创建好框传给它,使其使用这个框
Thread tp = new Thread(p);//接口方法创建一个生产者线程
Thread tc = new Thread(c);//接口方法创建一个消费者线程
tp.start();//开启生产者线程
tc.start();//开启消费者线程
}
} //馒头类
class SteamBread{
int id;
SteamBread(int id){
this.id = id;
} @Override
public String toString() { //重写馒头返回的方法
return "SteamBread:" + id;
}
} //装馒头的框,栈结构
class SyncStack{
int index = 0;
SteamBread[] stb = new SteamBread[6]; //构造一个装馒头的数组,容量是6 //放入框中,相当于入栈
public synchronized void push(SteamBread sb){
while (index==stb.length){ //筐满了,即栈满,
try {
this.wait();//让当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();//唤醒在此对象监视器上等待的单个线程,即消费者线程
stb[index]=sb;
this.index++;
} //从框中拿出,相当于出栈
public synchronized SteamBread pop(){
while (index==0){//筐空了,即栈空
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
this.index--;//push第n个之后,this.index++,使栈顶为n+1,故return之前要减一
return stb[index];
}
} //生产者类,实现了Runnable接口,以便于构造生产者线程
class Producer implements Runnable{
SyncStack ss = null;
Producer(SyncStack ss){
this.ss = ss;
} @Override
public void run() {
// 开始生产馒头
for (int i = 0; i < 20; i++) {
SteamBread stb = new SteamBread(i);//生产一个新的包子
ss.push(stb);//把包子存到框里面
System.out.println("生产了"+stb);//打印出我们生产了一个包子,注意此处stb本身是有一个返回值的,这这里将被调用
try {
Thread.sleep(10);//每生产一个馒头,睡觉10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} //消费者类,实现了Runnable接口,以便于构造消费者线程
class Consume implements Runnable{
SyncStack ss = null;
public Consume(SyncStack ss){
super();
this.ss = ss;
} @Override
public void run() {
//开始消费馒头
for (int i = 0; i < 20; i++) {
SteamBread stb = ss.pop();
System.out.println("消费了"+stb);
try {
Thread.sleep(10);//每消费一个馒头,睡觉100毫秒。即生产多个,消费一个
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

代码版权是thinkpadshi博主的,写的挺好的。

2.知识点讲解

线程通信-wait()和notify()方法介绍:

java.lang.bjec类提供了这两类方法用于线程通信

wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他线程唤醒该线程

notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待

notifyAll():执行该方法的线程唤醒在等待池中等待的所有线程,把线程转到锁池中等待

注意:上述方法只能被同步监听锁对象那个来调用,否则报错:

建设A线程和B线程共同操作一个X对象。A,B线程可以通过X对象的wait()和notify()方法来进行通信,流程如下:

1.当A线程执行X对象的同步方法时,A线程持有X对象的锁,B线程在X对象的锁池中等待

2.A线程在同步方法中执行X.wait()方法时,A线程释放X对象的锁,并进入X对象的等待池

3.在X对象的锁池中等待的B线程获取X兑现的锁,执行X的另一个同步方法

4.B线程在同步方法中执行X.notify()方法时,JVM把A线程从X对象的等待池中移动到X对象的锁池中,等待获取锁

5.B线程执行完同步方法,释放锁A线程获得锁,继续执行同步方法

解释一下:

等待池指的是线程现在还没有能力去抢锁,所以被放在一边被等待赋予抢锁的能力。

有点像你投简历找工作需要经历简历和面试两个关卡,简历刚投到一家公司的时候,你现在还不具备去面试的资格,这个时候你的简历被放在一堆不被面试的文件夹里,这个文件夹叫做等待池文件夹

锁池就是线程具备了抢锁的能力,但是同时有多个线程来抢,这个时候线程就处在锁池里,然后等待谁抢到锁,谁就执行锁里面的的代码块。

同样,就像你的简历通过的筛选,通知你参加面试了,这个时候你就具备了争夺这个工作的机会,但是这个时候还是有很多的人跟你一样有面试机会,但最终只能录取一个人,至于录用谁就要看这个人的能力了。这个时候你的简历从等待池里面拿出来了,放在锁池里面。

为什么wait(),notify()都是Object类的方法:

多个线程只有使用相同的一个对象的时候,多线程之间才有互斥效果

我们把这个用来做互斥的对象称之为:同步监听对象/同步锁

同步锁可以选择任意类型的对象即可,只需要保证多个线程使用的是相同锁对象即可

因为,只有同步监听对象才能调用wait和notify方法,所以wait和notify方法应该存在与Object类中,而不是Thread类中

3.使用Lock(锁)方法

上面是使用普通的synchronized修饰符,多线程的同步操作有三种方式,现在看看Lock方法如何来写

使用Lock和Condition接口:

wait()和notify()方法,只能被同步监听锁对象来调用,否则报错IllegalMontiorStateException

那么,现在有一个问题,Lock机制是不需要同步锁的,他自己就是一个锁,这个时候当然也是没有自动获取锁和自动释放锁的概念的。

因为没有同步锁,所以,我们就不能使用wait()和notify()方法

那么** 解决方法是:**

java5中提供了Lock机制的同时提供了处理Lock机制的通信控制的Condition接口

** 具体就是:**

1.使用Lock机制取代synchronized代码块和synchronized方法

2.使用Condition接口对象的await,signal,signalAll方法取代Object类中的wait,notify,notifyAll方法

** 解释一下:**

await()和wait()方法作用是相同相同的

signal()和notify()方法的作用是完全相同的

signalAll()和notifyAll()方法的作用是完全相同的

具体使用方法:

Condition本身是Lock的一个内部类,实例实质上被绑定到一个锁上,要为特定的锁Lock实例获得Condition实例,请使用newCondition()方法

生产者/消费者模型里面有两个类,所以我们要创建两个锁,分别用在不同的额

代码示例:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /**
* 生产者消费者问题,涉及到几个类
* 第一,这个问题本身就是一个类,即主类
* 第二,既然是生产者、消费者,那么生产者类和消费者类就是必须的
* 第三,生产什么,消费什么,所以物品类是必须的,这里是馒头类
* 第四,既然是线程,那么就不是一对一的,也就是说不是生产一个消费一个,既然这样,多生产的往哪里放
* 现实中就是筐了,在计算机中也就是数据结构,筐在数据结构中最形象的就是栈了,因此还要一个栈类
*/ public class ProduceConsume {
public static void main(String[] args) {
SyncStack ss = new SyncStack();//建造一个装馒头的框
Producer p = new Producer(ss);//新建一个生产者,并把已经创建好框传给它,使其使用这个框
Consume c = new Consume(ss);//新建一个消费者,并把已经创建好框传给它,使其使用这个框
Thread tp = new Thread(p);//接口方法创建一个生产者线程
Thread tc = new Thread(c);//接口方法创建一个消费者线程
tp.start();//开启生产者线程
tc.start();//开启消费者线程
}
} //馒头类
class SteamBread{
int id;
SteamBread(int id){
this.id = id;
} @Override
public String toString() { //重写馒头返回的方法
return "SteamBread:" + id;
}
} //装馒头的框,栈结构
class SyncStack{
int index = 0;
SteamBread[] stb = new SteamBread[6]; //构造一个装馒头的数组,容量是6
final Lock lock = new ReentrantLock(); //创建一个锁
final Condition condition = lock.newCondition(); //为lock锁创建一个Conditon实例 //放入框中,相当于入栈
public void push(SteamBread sb){
lock.lock();//获取锁
try {
while (index==stb.length) { //筐满了,即栈满,
condition.await();//当前线程等待
}
condition.signal();//唤醒另外一个线程
stb[index]=sb;
this.index++;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();//释放锁
} } //从框中拿出,相当于出栈
public SteamBread pop(){
lock.lock();//获取锁
try {
while (index==0) {//筐空了,即栈空
condition.await();//当前线程等待
}
this.index--;//push第n个之后,this.index++,使栈顶为n+1,故return之前要减一
condition.signal();//唤醒另外一个线程
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
return stb[index];
}
}

4.死锁

多线程通信的时候是非常容易造成死锁的,死锁是无法解决的,只能避免:

当A线程等待B线程所持有的锁,而B线程也在等在A线程所持有的锁时,这个收会发生死锁现象,对于死锁JVM是不检测的

由于死锁不会被检测出来,所以只能由程序员来保证线程不会导致死锁

最有名的死锁问题就是:哲学家吃面条的问题 **

避免死锁法则:当多个线程都要访问共享的资源A,B,C时,保证每一个线程都按照相同的顺序去访问他们,比如都都先访问A,接着B,最后C

** Thread类中一些过时的用法


suspend();使正在运行的线程放弃CPU,暂停运行

resume():是暂停的线程恢复运行

过时的用法是非常容易导致死锁的,所以不可再用

** 死锁情况:**

A线程获得对象锁,正在执行一个同步方法,如果B线程调用A线程的suspend()方法,此时A线程暂停运行,此时A线程放弃CPU,但是不会放弃暂用的锁,此时就造成了死锁

第23章 java线程通信——生产者/消费者模型案例的更多相关文章

  1. Java线程通信-生产者消费者问题

    线程通信示例——生产者消费者问题 这类问题描述了一种情况,假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费.假设仓库中没有产品,则生产者可以将 产品放入仓库,有 ...

  2. Java多线程-同步:synchronized 和线程通信:生产者消费者模式

    大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...

  3. .net学习之多线程、线程死锁、线程通信 生产者消费者模式、委托的简单使用、GDI(图形设计接口)常用的方法

    1.多线程简单使用(1)进程是不执行代码的,执行代码的是线程,一个进程默认有一个线程(2)线程默认情况下都是前台线程,要所有的前台线程退出以后程序才会退出,进程里默认的线程我们叫做主线程或者叫做UI线 ...

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

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

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

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

  6. 线程锁,threadinglocal,线程池,生产者消费者模型

    1.线程锁 1.锁Lock(只能锁一次) import threading import time v = [] lock = threading.Lock() def func(arg): lock ...

  7. java线程之生产者消费者

    看了毕向东老师的生产者消费者,就照着视频参考运行了一下,感觉还好 这个值得学习的是条理特别清晰: ProducterConsumerDemo.java中,一个资源类Resources,生产者消费者都可 ...

  8. Java 线程池 +生产者消费者+MySQL读取300 万条数据

    1.1需求 数据库300 万条用户数据 ,遍历获取所有用户, 各种组合关联, 获取到一个新的json ,存到redis 上. 1.2 难点 数据库比较多, 不可能单线程查询所有的数据到内存. 1.3解 ...

  9. java并发之生产者消费者模型

    生产者和消费者模型是操作系统中经典的同步问题.该问题最早由Dijkstra提出,用以演示它提出的信号量机制. 经典的生产者和消费者模型的描写叙述是:有一群生产者进程在生产产品.并将这些产品提供给消费者 ...

随机推荐

  1. css媒体查询

    简单解释:http://zh.learnlayout.com/media-queries.html 深入学习1:https://developer.mozilla.org/en-US/docs/Web ...

  2. Java 并发编程 Executor

    Executor框架是指java 5中引入的一系列并发库中与executor相关的一些功能类,其中包括线程池,Executor,Executors,ExecutorService,Completion ...

  3. DB2物化视图——MQT 物化查询表的正确使用(materialized query tables)

    我们今天主要向大家讲述的是DB2物化视图——MQT 物化查询表使用,以下就是对DB2物化视图之MQT物化查询表的正确使用的主要内容的详细描述,望大家在浏览之后会对其有更深的了解. MQT 的定义基于查 ...

  4. hdu 5493 (树状数组)

    题意:在一个队列中,你知道一个人在他左边或者右边比他高的人的个数,求字典序最小的答案 思路:先将人按  矮-->高 排序,然后算出在每个人前面需要预留的位置.树状数组(也可以线段树)解决时,先二 ...

  5. 完全关闭Hyper-v的方法

    众所周知Hyper-v和vmware有冲突,开启Hyper-v功能vmware就不能使用,但即使关闭了也是如此,这是因为功能没有被完全关闭,这里整理下方法,我自己在两台机子亲测有效. win+x,a, ...

  6. 100Mbps和100Mb/s有什么不同

    100Mbps 和 100Mb/s 有什么不同 Mbps=Mbit/s即兆比特每秒.Million bits per second的缩写 传输速率是指设备的的数据交换能力,也叫“带宽”,单位是Mbps ...

  7. java学习--java源文件

    java源文件以“java”为扩展名.源文件的基本组成部分是类(class) 一个源文件中最多只能有一个public类.其他类(如抽象类,default类即省去修饰符的类,接口)的个数不限. 如果源文 ...

  8. 20155335俞昆 《java程序设计》第八周总结

    2016-2017-2 <Java程序设计>第X周学习总结 ##认识NIO 在java中,输入与输出,基本上是以字节为单位进行的低层次处理,实际上多半是对字节数组中整个区块进行处理,对于d ...

  9. EBADF, read

    nodejs读取文件出的一个错误,解决不了,自己技术还达不到,解决不了这么高深的问题. 描述:需要记录访问的人数,每个人随机到的酒.打算用json文件来存储:read count write coun ...

  10. flink统计根据账号每30秒 金额的平均值

    package com.zetyun.streaming.flink; import org.apache.flink.api.common.functions.MapFunction;import ...