将上篇博客的代码稍作修改
package com.javaxl.thread;
/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create 2019-05-29 21:34
* <p>
* 操作同一个资源
* 资源中只有资源数量
* 生产者对应一个生产的线程
* 消费者对应一个消费的线程
*/
public class Demo4 {
public static void main(String[] args) {
// 一个资源池
Res r = new Res();
Thread in = new Thread(new Input(r));
Thread out = new Thread(new Output(r));
in.start();
out.start();
// Thread in2 = new Thread(new Input(r));
// Thread out2 = new Thread(new Output(r));
// in2.start();
// out2.start();
}
}
class Res {
private boolean flag = false;
private int num = 0;
/**
* 往资源池中添加资源
*/
public synchronized void set(){
if (this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"----------生产者---"+(++num));;
this.flag = true;
this.notify();
}
/**
* 使用资源池中的资源
*/
public synchronized void out(){
if(!this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-消费者---"+num);;
this.flag = false;
this.notify();
}
}
/**
* 往资源池中生产
*/
class Input implements Runnable {
private Res r;
public Input(Res r) {
this.r = r;
}
@Override
public void run() {
while (true) {
synchronized (r) {
r.set();
}
}
}
}
/**
* 从资源池消费
*/
class Output implements Runnable {
private Res r;
public Output(Res r) {
this.r = r;
}
@Override
public void run() {
while (true) {
synchronized (r) {
r.out();
}
}
}
}
上面这段代码,运行后会发现,如果只有一个生产者一个消费者,那么没有任何问题,资源存一个取一个;
如果有多个生产者多个消费者,那么又出现了并发问题,这个并发问题的产生是线程间的通信问题。
下面这个图是正常现象

多生产者多消费者线程操作的异常现象截图

下面来分析一下Res中的代码

这个代码一个生产者一个消费那么代码不会有并发问题
但是如果有多个生产者多个消费者,那么代码就会出现并发问题了
这里以两个生产者、两个消费者为例
那么会出现什么现象呢?
会出现生产出两个商品,而只消费了一个;
或者生产出一个商品,但是消费了两次的问题;
为什么会出现并发问题呢?
我们来分析上面代码,假设t1、t2是两个生产者线程,t3、t4是两个消费者线程;
1、t1作为生产者执行set方法,先 生产出一个商品,然后flag=true,线程放弃资格,进入等待状态;
2、T2再 进来,由于flag=true,t2线程直接放弃资格,进入等待状态;
3、T3作为消费者执行out方法,因为flag=true,直接消费了一个商品,然后flag=false,线程放弃资格,进入等待状态,并且唤醒第一个等待的线程t1,线程t1获取资格;
4、T1执行线程内容无需做flag判断,这个时候t1线程生成出一个商品,这时flag=true,并且唤醒了t2;(因为t1被唤醒,所以具有抢夺锁资格的有t1、t4线程,这里为了讲解并发现象,我们假设t1抢到了锁,获取了cpu的执行权)
5、T2被唤醒也不需要做flag判断,t2直接就又生产出一个商品,然后flag=true,并且唤醒了t3,t3线程也获取cpu的执行资格,这个时候又回到了最初的状态,四个线程都具备cpu执行权。但是从这一段分析中,我们发现第四、第五连续生产了两次商品,而只消费了一次;
反之亦然,也可能消费了两次,但是只生产了一次商品,这里就不做分析了;
通过上面的分析,我们知道造成其结果的根本原因在于,线程被唤醒后,不会再做if(flag)这个判断了;
那么我们的改进办法就是将if(flag)改成while(flag);
这样做就可以解决,线程被唤醒后不再做flag判断的问题,但是又出现了新的问题;
这时候会出现四个线程同时放弃资格,处于等待状态的情况;
其根本原因在于notify()每次只能唤醒本方线程,不能唤醒对方线程;
那么解决方法是:
将notify()改为notifyAll()就可以了;
相关代码:
package com.javaxl.thread;
/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create 2019-05-29 21:34
* <p>
* 操作同一个资源
* 资源中只有资源数量
* 生产者对应一个生产的线程
* 消费者对应一个消费的线程
*/
public class Demo4 {
public static void main(String[] args) {
// 一个资源池
Res r = new Res();
Thread in = new Thread(new Input(r));
Thread out = new Thread(new Output(r));
in.start();
out.start();
Thread in2 = new Thread(new Input(r));
Thread out2 = new Thread(new Output(r));
in2.start();
out2.start();
}
}
class Res {
private String name;
private String sex;
private boolean flag = false;
private int num = 0;
public Res(String name, String sex) {
this.name = name;
this.sex = sex;
}
public Res() {
}
/**
* 往资源池中添加资源
*/
public synchronized void set(){
while (this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"----------生产者---"+(++num));;
this.flag = true;
this.notifyAll();
}
/**
* 使用资源池中的资源
*/
public synchronized void out(){
while (!this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-消费者---"+num);;
this.flag = false;
this.notifyAll();
}
}
/**
* 往资源池中生产
*/
class Input implements Runnable {
private Res r;
public Input(Res r) {
this.r = r;
}
@Override
public void run() {
while (true) {
synchronized (r) {
r.set();
}
}
}
}
/**
* 从资源池消费
*/
class Output implements Runnable {
private Res r;
public Output(Res r) {
this.r = r;
}
@Override
public void run() {
while (true) {
synchronized (r) {
r.out();
}
}
}
}
备案号:湘ICP备19000029号
Copyright © 2018-2019 javaxl晓码阁 版权所有