首页 > 其他 > 详细

3、生产者和消费者问题

时间:2020-05-24 14:22:47      阅读:34      评论:0      收藏:0      [点我收藏+]

这是面试高频:还有其它的:单例模式、8大排序算法、死锁;

synchronized版

两个线程的情况

package com.zxh.demo01;

/**
 * 线程之间的通信问题:也就是生产者和消费者问题!
 * 如何做到通信:等待唤醒,通知唤醒
 * 模拟多个线程操作一个变量:对 num 进行加1、减1操作
 * A num + 1
 * B num - 1
 */

public class A {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

    }
}
// 如何实现通信,分三步:1、判断等待,2、业务操作,3、通知唤醒
class Data{ //资源类,数字,实现低耦合

    private int number = 0;

    // +1
    public synchronized void increment() throws InterruptedException {
        if(number != 0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + " => " + number);
        // 通知其他线程,我执行 +1 完毕
        this.notifyAll();
    }

    // -1
    public synchronized void decrement() throws InterruptedException {
        if(number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + " => " + number);
        // 通知其他线程,我执行 -1 完毕
        this.notifyAll();
    }


}

顺利执行:

技术分享图片

存在虚假唤醒问题

更多的线程参与,A和C进行+1操作,B和D进行-1操作,就会导致问题

new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try {
            data.increment();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, "A").start();
new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try {
            data.decrement();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, "B").start();
new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try {
            data.increment();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, "C").start();
new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try {
            data.decrement();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, "D").start();

技术分享图片

解决虚假唤醒问题

问题:就是存在虚假唤醒

什么是虚假唤醒?

不需要唤醒的线程也别唤醒了,比如,B执行后,A和C都被唤醒。那么会发生什么呢?接下来看:

  • 现在 A和C 进行的是 +1 操作,B和D 进行的是 -1 操作

假设程序运行的过程:

  1. A 进行了 +1 操作后等待,此时进入C 等待,再进入B进行了 -1 操作后唤醒所有线程并等待

  2. 这个时候A 和 C 都在这个位置

  3. 技术分享图片
  4. 在这个位置被唤醒,那么假设A先拿到锁,进行了 +1 操作,并唤醒其他线程,判断 number != 0,A线程等待,释放了+1方法的锁。
  5. 虽然唤醒了BD线程,但是线程C并没有在等待,并且到了+1方法的锁,如果CPU调度到它,那么又会进行一次 +1 操作,结果就会有出现2。

 

如何解决呢?

在第4步的时候,C紧接着A后面被调度到,此时的number = 1,显然不能再进行 +1 操作了,但是 if 语句不能再次进行判断,所以就可以使用while语句再次进行判断,是否可以操作即可。

 

在jdk8的源码中,wait()方法中,就有如下说明:技术分享图片

 

 

 修改代码,将if判断改为while循环,进行多次判断,这样就解决了虚假唤醒

/**
 * 线程之间的通信问题:也就是生产者和消费者问题!
 * 如何做到通信:等待唤醒,通知唤醒
 * 模拟多个线程操作一个变量:对 num 进行加1、减1操作
 * A num + 1
 * B num - 1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();

    }
}
// 如何实现通信,分三步:1、判断等待,2、业务操作,3、通知唤醒
class Data{ //资源类,数字,实现低耦合

    private int number = 0;

    // +1
    public synchronized void increment() throws InterruptedException {
        while(number != 0){
            this.wait();    //A C
        }
        number++;
        System.out.println(Thread.currentThread().getName() + " => " + number);
        // 通知其他线程,我执行 +1 完毕
        this.notifyAll();
    }

    // -1
    public synchronized void decrement() throws InterruptedException {
        while(number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + " => " + number);
        // 通知其他线程,我执行 -1 完毕
        this.notifyAll();
    }


}

JUC版

  • 使用的是Lock锁

Lock用什么替换sync版

使用JUC版的话,需要知道原来的3剑客用什么替换了?技术分享图片

  • 文档中Lock接口中提到一个方法,是获取一个Condition接口的实现类ConditionObject对象

  • 技术分享图片

  • 在源码中可以看到

  • 技术分享图片

  • 该方法具体的说明,也可以进行等待和唤醒操作

  • 技术分享图片

  • 点进去这个Condition接口,可以发现是Locks包下的

  • 技术分享图片

  • 具体的说明如下:该接口取代了对象监视器方法的使用

  • 技术分享图片

  • 文档里页举例该对象的使用方法,await()等待、signal()唤醒

  • 技术分享图片

代码实现

package com.zxh.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程之间的通信问题:也就是生产者和消费者问题!
 * 如何做到通信:等待唤醒,通知唤醒
 * 模拟多个线程操作一个变量:对 num 进行加1、减1操作
 * A num + 1
 * B num - 1
 */

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();

    }
}

// 如何实现通信,分三步:1、判断等待,2、业务操作,3、通知唤醒
class Data2{ //资源类,数字,实现低耦合

    private int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //condition.await();  //等待
    //condition.signalAll();  //唤醒所有

    // +1
    public void increment() throws InterruptedException {
        lock.lock();    // 加锁
        try {
            // 业务
            while(number != 0){
                //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + " => " + number);
            // 通知其他线程,我执行 +1 完毕
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // 解锁
        }
    }

    // -1
    public void decrement() throws InterruptedException {
        lock.lock();    // 加锁
        try {
            // 业务
            while(number == 0){
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + " => " + number);
            // 通知其他线程,我执行 -1 完毕
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // 解锁
        }
    }


}

存在问题

技术分享图片

任何一个新的技术,绝不仅仅是覆盖了原来的技术,肯定会有它的优势和补充!

解决问题

那么要如何解决呢?

Condition 精准通知和唤醒线程

举例

有3个线程ABC,现在想让这3个线程的执行顺序是固定的:A -> B -> C,依次循环。

  • 以之前所掌握的知识,没有办法解决的。

  • JUC中 Condition 接口,相当于一个对象资源监视器,要想做到精准唤醒,使用多个监视器进行控制即可!

  • 比如:在购物中,用户下单 -> 支付 -> 商品数量 - 1,每个步骤都有一个资源监视器,执行过程中,只需要唤醒对应的监视器就可以了。

 

测试代码

package com.zxh.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 3个线程ABC,现在想让这3个线程的执行顺序是固定的:A -> B -> C,依次循环。
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        }, "A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        }, "B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        }, "C").start();

    }
}
class Data3{    // 资源类

    private int number = 1; // 用于控制的变量

    private Lock lock = new ReentrantLock();
    //资源监视器,现有3个线程,创建3个对应的资源监视器
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void printA() {
        lock.lock();    //加锁
        try {
            while(number != 1){
                condition1.await(); // 等待
            }
            // 业务
            number = 2;
            System.out.println(Thread.currentThread().getName() + "=> AAAAA");
            // 唤醒,需要唤醒的是指定的线程 B 的监视器
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // 解锁
        }
    }

    public void printB(){
        lock.lock();    // 加锁
        try {
            while(number != 2){
                condition2.await(); // 等待
            }
            // 业务
            number = 3;
            System.out.println(Thread.currentThread().getName() + "=> BBBBBB");
            //唤醒,需要唤醒指定线程 C 的监视器
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // 解锁
        }
    }

    public void printC(){
        lock.lock();    // 加锁
        try {
            while(number != 3){
                condition3.await(); // 等待
            }
            // 业务
            number = 1;
            System.out.println(Thread.currentThread().getName() + "=> CCCCCCC");
            //唤醒,需要唤醒指定线程 A 的监视器
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // 解锁
        }
    }

}

成功做到精准通知

技术分享图片

3、生产者和消费者问题

原文:https://www.cnblogs.com/zxhbk/p/12950855.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!