栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

生产者消费者问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

生产者消费者问题

基础知识
  • o.wait() 方法:让正在 o 对象上活动的线程进入等待,并且释放该线程占有的锁。
  • o.notiry() 方法:让正在 o 对象上等待的某个线程唤醒。
  • o.notifyAll() 方法:让正在 o 对象上等待的所有线程唤醒。
  • 注:线程被唤醒以后会从上次等待的位置继续往下执行,也就是会接着执行 wait() 方法之后的代码。
  • Conditon 接口中的 await() 对应 Object 中的 wait();
    Condition 接口中的 signal() 对应 Object 中的 notify();
    Condition 接口中的 signalAll() 对应 Object 中的 notifyAll()。
Synchronized 版本的生产者消费者问题
public class Test7 {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() ->{
            for(int i = 0; i < 10; i++){
                data.produce();
            }
        }, "A").start();
        new Thread(() ->{
            for(int i = 0; i < 10; i++){
                data.consume();
            }
        }, "B").start();
        new Thread(() ->{
            for(int i = 0; i < 10; i++){
                data.produce();
            }
        }, "C").start();
        new Thread(() ->{
            for(int i = 0; i < 10; i++){
                data.consume();
            }
        }, "D").start();
    }
}
class Data{
    int num = 0;
    // 步骤:判断等待、业务、通知唤醒
    public synchronized void produce(){
        // 线程被唤醒之后是会从上次等待的位置继续往下执行,也就是会接着执行 wait() 方法之后的代码
        // 所以假设 A、C 线程同时被唤醒以后,如果是 if,那么不会再次进行判断,A、C线程下一条要执行的语句都是下面的 num++,所以会出现 2 的情况。而如果是 while,那么会再次执行判断,此时只能有一个线程执行到 num ++ 语句
        while(num != 0){ // 不能是 if
            try {
                this.wait(); // 当前线程进入等待状态,并且释放 data 对象的锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num++;
        System.out.println(Thread.currentThread().getName() + " -> " + num);
        this.notifyAll();
    }
    // 步骤:判断等待、业务、通知唤醒
    public synchronized void consume(){
        while(num == 0){ // 不能是 if
            try {
                this.wait(); // 当前线程进入等待状态,并且释放 data 对象的锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        System.out.println(Thread.currentThread().getName() + " -> " + num);
        this.notifyAll();
    }
}
Lock 版本的生产者消费者问题
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test8 {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(() ->{
            for(int i = 0; i < 10; i++){
                data.produce();
            }
        }, "A").start();
        new Thread(() ->{
            for(int i = 0; i < 10; i++){
                data.consume();
            }
        }, "B").start();
        new Thread(() ->{
            for(int i = 0; i < 10; i++){
                data.produce();
            }
        }, "C").start();
        new Thread(() ->{
            for(int i = 0; i < 10; i++){
                data.consume();
            }
        }, "D").start();
    }
}
class Data2{
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    int num = 0;
    // 步骤:判断等待、业务、通知唤醒
    public void produce(){
        lock.lock();
        try {
            while(num != 0){
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + " -> " + num);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    // 步骤:判断等待、业务、通知唤醒
    public void consume(){
        lock.lock();
        try {
            while(num == 0){
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + " -> " + num);
            condition.signalAll();
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  • 上面两种虽然可以实现生产者消费者问题,但是线程执行的顺序是不固定的,如果我们要想某个线程执行完之后指定另一个线程执行,则需要下面的方法。
使用 Condition 来精准地唤醒某个线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// Condition 可以用来唤醒指定的线程
// 现在有三个线程 A、B、C,现在要 A 线程执行完了之后去通知 B 线程执行,B 线程执行完了之后去通知 C 线程执行,C 线程执行完了之后去通知 A 线程执行
public class Test9 {
    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{
    Lock lock = new ReentrantLock();
    // 通过监视器判断我要唤醒的是哪一个线程
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    int num = 1; // num 为 1 时让线程 A 执行,num 为 2 时让线程 B 执行,num 为 3 时让线程 C 执行
    // 步骤:判断等待、业务、通知唤醒
    public void printA(){
        lock.lock();
        try {
            while(num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + " 线程执行了");
            num = 2;
            condition2.signal(); // 唤醒线程 B
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    // 步骤:判断等待、业务、通知唤醒
    public void printB(){
        lock.lock();
        try {
            while(num != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + " 线程执行了");
            num = 3;
            condition3.signal(); // 唤醒线程 C
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    // 步骤:判断等待、业务、通知唤醒
    public void printC(){
        lock.lock();
        try {
            while(num != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + " 线程执行了");
            num = 1;
            condition1.signal(); // 唤醒线程 A
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
补充 1:synchronized 的使用方法
  1. 修饰代码块。指定一个线程共享的对象,给其加锁
synchronized(线程共享对象){
    同步代码块
}
  1. 修饰实例方法,在实例方法上使用 synchronized。表示对当前实例对象 this 加锁,并且同步代码块是整个方法体。
public synchronized void f(){}
  1. 修饰静态方法,在静态方法上使用 synchronized。表示对当前类的 Class 对象加锁。多个线程执行同一个类中被 synchronized 修饰的静态方法时有且只有一个线程能够获取当前类的 Class 对象锁并执行方法,其它线程等待(注:一个类的类锁永远只有一把)
public synchronized static void f(){}
补充 2:Lock 的使用方法
Lock lock = new RenntrantLock();

lock.lock(); // 加锁
try{
    同步代码块
} catch (Exception e) {
    e.printStackTrace();
} finally {
    lock.unlock(); // 解锁
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/657528.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号