将上篇博客的代码稍作修改
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晓码阁 版权所有