lock(condition)实现精准通知唤醒线程和Lock版的生产者消费者问题

简介: lock(condition)实现精准通知唤醒线程

上代码


package com.wyh.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @program: JUC
 * @description: Condition实现精准通知唤醒
 * @author: 魏一鹤
 * @createDate: 2022-02-13 19:20
 **/
//利用Condition实现精准通知唤醒
//我们想要的执行顺序,A执行完执行B B执行完执行C C执行完执行A
public class C {
public static void main(String[] args){
//静态资源类
        Data3 data3 = new Data3();
        //多个线程操作同一个静态资源类(并发) 简化语法使用lambda表达式 10次for循环执行方法
        //如果for循环里面只有一行代码 可以省去大括号"{}"
        new Thread(()->{ for (int i = 0; i < 10; i++) data3.printA(); },"线程A").start();
new Thread(()->{ for (int i = 0; i < 10; i++) data3.printB(); },"线程B").start();
new Thread(()->{ for (int i = 0; i < 10; i++) data3.printC(); },"线程C").start();
    }
}
//资源类
class Data3{
//lock可重入锁
    Lock lock=new ReentrantLock();
    //使用lock创建一个同步监视器 创建三个,监视不同的对象  利用Condition实现精准通知
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();
    //变量标识 如果等于1就是线程A执行  如果等于2就是线程B执行 如果等于3就是线程C执行
    private int num=1;
//A线程打印方法
    public void printA(){
try {
//加锁 业务代码也写在try中
            lock.lock();
//通知者消费者写代码思路 :判断等待 执行代码  通知其他线程
            //为了防止虚拟唤醒 使用while循环判断
            while (num!=1) {
//等待
                conditionA.await();
            }
            System.out.println(Thread.currentThread().getName() + "AAAAAAA");
//num=2 线程B执行
            num=2;
          //精准唤醒conditionB 不使用signalAll方法唤醒全部 而是调用某一个具体的同步监视器唤醒具体的
            conditionB.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
//解锁
            lock.unlock();
        }
    }
//B线程打印方法
    public void printB(){
try {
//加锁 业务代码也写在try中
            //通知者消费者写代码思路 :判断等待 执行代码  通知其他线程
            lock.lock();
//为了防止虚拟唤醒 使用while循环判断
            while (num != 2) {
//唤醒
                conditionB.await();
            }
            System.out.println(Thread.currentThread().getName() + "BBBBBBB");
//num=3 线程C执行
            num=3;
//精准唤醒conditionC
            conditionC.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
//解锁
            lock.unlock();
        }
    }
//C线程打印方法
    public void printC(){
try {
//加锁 业务代码也写在try中
            //通知者消费者写代码思路 :判断等待 执行代码  通知其他线程
            lock.lock();
//为了防止虚拟唤醒 使用while循环判断
            while (num != 3) {
//唤醒
                conditionC.await();
            }
            System.out.println(Thread.currentThread().getName() + "CCCCCC");
//num=1 线程A执行
            num=1;
            //精准唤醒conditionA
            conditionA.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
//解锁
            lock.unlock();
        }
    }
}


Lock版的生产者消费者问题




线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC

线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC

线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC

线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC

线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC

线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC

线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC

线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC

线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC

线程AAAAAAAA

线程BBBBBBBB

线程CCCCCCC



线程之间的通信问题 生产者和消费者问题

生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程

    判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线

  新版(JUC)生产者和消费者问题

  回顾:传统的synchronized解决生产者和消费者问题有wait(等待)和notify(唤醒)方法,那么使用JUC实现生产者和消

通过lock找到condition(它是一个同步监视器),使用condition的await()方法和signalAll(唤醒全部)方法


//创建Lock锁实现类可重入锁
Lock lock = new ReentrantLock();
//通过可重入锁创建condition对象 它是一个同步监视器
Condition condition = lock.newCondition();
//等待     相当于synchronized的wait() 也需要抛异常
condition.await();
//唤醒全部 相当于synchronized的nofityAll()
condition.signalAll();

synchronized:wait(等待) notify(唤醒)

lock-->contidition:await(等待) signaAlll(唤醒全部)


package com.wyh.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @program: JUC
 * @description: 使用JUC下的lock下的condition的await和signal实现消费者生产者线程通信
 * @author: 魏一鹤
 * @createDate: 2022-02-12 22:34
 **/
//使用JUC下的lock下的condition的await和signal实现消费者生产者线程通信
public class B {
public static void main(String[] args){
//线程操作资源类
        //创建资源类
        Data2 data=new Data2();
//多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称
        new Thread(()->{
//从10循环+1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程A").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程B").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程C").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程D").start();
    }
}
//静态资源  独立耦合的 必须降低耦合性
class Data2{
// 属性 数字变量
    private  int num=0;
 //创建Lock锁实现类可重入锁
    Lock lock = new ReentrantLock();
    //通过可重入锁创建condition对象 它是一个同步监视器
    Condition condition = lock.newCondition();
//方法 数字+1 自增1
    public  void increment() throws InterruptedException {
        //IDEA中try catch快捷键 选中代码ctrl+alt+t 活在在菜单栏选择code选中try catch自动生成代码块
        //lock需要和try catch finally代码块配合使用 try中加锁(lock)并且写入业务代码,finally中解锁(unLock)
        try {
//加锁
            lock.lock();
//为了防止虚假唤醒,应该用while进行判断等待
            while (num!=0){
 //等待 相当于synchronized的wait()  和wait一样也需要抛异常
                condition.await();
            }
num++;
            System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我+1完毕了
            //唤醒全部 相当于synchronized的notifyAll()
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
//解锁
            lock.unlock();
        }
    }
//方法 数字-1 自减1
    public  void decrement() throws InterruptedException {
        //IDEA中try catch快捷键 选中代码ctrl+alt+t 活在在菜单栏选择code选中try catch自动生成代码块
        //lock需要和try catch finally代码块配合使用 try中加锁(lock)并且写入业务代码,finally中解锁(unLock)
        try {
 //加锁
            lock.lock();
 //为了防止虚假唤醒,应该用while进行判断等待
            while(num==0){
//等待 相当于synchronized的wait()  和wait一样也需要抛异常  和wait一样也需要抛异常
                condition.await();
            }
num--;
            System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我-1完毕了
            //唤醒全部 相当于synchronized的notifyAll()
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
 //解锁
            lock.unlock();
        }
    }
}


观察控制台发现效果和上面传统的synchronized效果是一样的,不同之处是使用的对象以及方法不同

image.png


通过观察结果,发现线程执行并没有安装我们想要的ABCD四个线程有序轮流执行,处于随机分布的执行状态,和synchronized执行顺序一样,那么既然这样,为什么不使用传统的synchronized而原则condition(lock)呢?

任何一个新技术,绝对不是仅仅只是覆盖了原来的老技术,肯定在老技术的基础上新增了优势和补充

condition精准的通知和唤醒线程:上面线程执行是随机分配的,不是有序执行的,如果想按照有序执行,那么应该是A->B->C->D这样的执行顺序,那么condition就可以帮我们实现这点,这也是它比传统synchronized的优势

目录
相关文章
|
1天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
1月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
47 4
|
2月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
25 1
|
2月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
31 2
|
3月前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
43 7
|
2月前
|
消息中间件 NoSQL 关系型数据库
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
33 0
|
4月前
|
Java
在Java多线程领域,精通Lock接口是成为高手的关键。
在Java多线程领域,精通Lock接口是成为高手的关键。相较于传统的`synchronized`,Lock接口自Java 5.0起提供了更灵活的线程同步机制,包括可中断等待、超时等待及公平锁选择等高级功能。本文通过实战演练介绍Lock接口的核心实现——ReentrantLock,并演示如何使用Condition进行精确线程控制,帮助你掌握这一武林秘籍,成为Java多线程领域的盟主。示例代码展示了ReentrantLock的基本用法及Condition在生产者-消费者模式中的应用,助你提升程序效率和稳定性。
52 2
|
4月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
32 2
|
3天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
12 1
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
60 1