线程通信基础示例(synchronized 与 Lock + Condition实现线程通信)

2023-11-09

一. synchronized 实现线程通讯

  1. 什么是线程通讯,可以将线程分为生产者线程与消费者线程,生产者线程创建共享数据(写),给消费者线程使用(读)
  2. 实现线程通讯的基本流程
  • 多个线程操作的资源类
  • 提供判断当前该生产还是消费的标识
  • 生产线程执行,会判断当前是否该生产,不需要,当前线程进入休眠状态,否则执行生产,生产完毕后唤醒消费线程进行消费
  • 消费线程执行,会判断当前是否该消费,不需要,当前线程进入休眠状态,否则执行消费,消费完毕后唤醒生产线程继续生产
  1. wait() 与 notify() 方法讲解
  • wati() 与 notify() 是 Object 总服务类提供的方法,
  • wati() 与 notify() 使用前提是在 synchronized 中
  • wati() 表示休眠并释放锁资源, notify() 表示唤醒当前此对象监视器上(同一锁下的)其它休眠线程
  1. wait() 与 join() 的区别: wait() 用在 synchronized 中, 被 wait() 的线程需要唤醒, 在一个方法中调用了 join(),表示当前线程进入阻塞状态,并且在其它线程执行完毕后才会自动唤醒执行,没有锁的概念
  2. wait() 与 sleep() 的区别: sleep 指定休眠时间,时间过后自动唤醒,并且sleep不会释放锁
  3. notify() 与 yield()区别: yield() 使当前线程重新回到可执行状态,但是并不会马上执行,没有锁的概念,notify() 是唤醒被 wait() 的线程,与 wait() 配合使用的

代码示例

  1. 多线程同时操作的资源类,注意点在判断标识时不可以使用 if 判断,因为一个线程在进入if 后进行了阻塞,后续再唤醒时,不会再去判断一遍,会造成生产消费,不一致的情况,使用 while 进行判断,当有线程进入 while 中被阻塞,在后续被唤醒会再执行一次 while,再去判断一次
//多线程操作数据流程
//1.多线程操作的资源类
//2.提供用来判断当前该生产还是消费的标识
//3.synchronized 方式下,通过 wait() 与 notify() 配合实现
class MyResource{
    //1.num 在当前可以看为是生产消费的数据,
    //同时也是判断当前该生产还是消费的标识
    private Integer num = 0;

    //2.生产方法
    public synchronized void production() throws Exception {
        //1.通过标识判断,当前是否生产,不可以则进入阻塞状态
        while (num != 0){
            this.wait();
        }
        //2.生产
        num++;
        System.out.println("执行生产"+num);
        //3.生产完毕唤醒同一监视器下的其它线程(也就是唤醒消费线程进行消费)
        this.notify();
    }

    //消费方法
    public synchronized void consumer() throws Exception{
        //1.通过标识判断,当前是否消费,不可以则进入阻塞状态
        while (num == 0){
            this.wait();
        }
        //2.消费
        num--;
        System.out.println("执行消费"+num);
        //3.消费完毕,唤醒同一监视器下的其它线程(也就是唤醒生产线程进行生产)
        this.notify();
    }
}
  1. 多线程执行生产,消费测试
public class T1 {
    public static void main(String[] args) {
    	//创建资源类对象
        MyResource resource = new MyResource();
        //1.使用匿名内部类方式创建 A 线程执行生产
        new Thread(()->{
            try {
                for(int i =0;i<50; i++){
                    resource.production();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        //2.使用匿名内部类方式创建 B 线程执行消费
        new Thread(()->{
            try {
                for(int i =0 ;i<50; i++){
                    resource.consumer();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

二. Lock + Condition 实现线程通讯

  1. 在生产或消费执行时,首先获取到 Lock 锁,
  2. 判断标识当前不可以生产,或消费时,通过 Condition 调用 await() 方法设置当前线程进入阻塞状态,相当于 wait()
  3. 当生产或消费完毕后通过 Condition 调用 signal() 唤醒其它线程
  4. 注意点: 防止发送异常 Lock 锁无法释放问题,在 finally 中释放锁

代码示例

  1. 多线程操作的资源类
class MyResource{
    //1.num 在当前可以看为是生产消费的数据,
    //同时也是判断当前该生产还是消费的标识
    private Integer num = 0;

    //2.创建 Lock 锁
    private Lock lock = new ReentrantLock();
    //3.创建 Condition
    private Condition condition = lock.newCondition();

    //2.生产方法
    public void production() throws Exception {

        //1.生产时首先获取到锁
        lock.lock();
        try{
            //2.通过标识判断,当前是否生产,不可以则进入阻塞状态
            while (num != 0){
                condition.await(); //相当于前面的 this.wait();
            }
            num++; //3.生产
            System.out.println("执行生产"+num);
            //4.生产完毕唤醒同一监视器下的其它线程(也就是唤醒消费线程进行消费)
            condition.signal(); //相当于前面的 this.notify();
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    //消费方法
    public void consumer() throws Exception{
        //1.消费时首先获取到锁
        lock.lock();
        try{
            //2.通过标识判断,当前是否消费,不可以则进入阻塞状态
            while (num == 0){
                condition.await();
            }
            num--;  //3.消费
            System.out.println("执行消费"+num);
            //4.消费完毕,唤醒同一监视器下的其它线程(也就是唤醒生产线程进行生产)
            condition.signal();
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
  1. 多线程生产消费执行测试
public class T1 {

    public static void main(String[] args) {
        MyResource resource = new MyResource();
        //1.使用匿名内部类方式创建 A 线程执行生产
        new Thread(()->{
            try {
                for(int i =0;i<50; i++){
                    resource.production();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        //2.使用匿名内部类方式创建 B 线程执行消费
        new Thread(()->{
            try {
                for(int i =0 ;i<50; i++){
                    resource.consumer();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

Lock + Condition 实现线程通讯的优点

  1. 问题: 在前面的生产消费测试中,会发现,A线程生产完毕后,B线程消费,假设同时存在 C 生产线程,生产出的数据要指定的 D线程消费,问题是生产线程执行完毕后如何精准通知到指定的消费线程进行消费
  2. 解决问题,可以创建多个 Condition,一个 Condition 对应线程,当前指定的生产或消费线程在不可以执行时使用指定的 Condition 调用 await() 进入阻塞,当生产或消费完毕后使用指定的 Condition 调用 signal() 唤醒对应的线程
  3. 多线程操作的资源类,提供多个 Condition,通过指定的 Condition 在方法中指定进行指定休眠,指定唤醒
class MyResource {
    //1.num 在当前可以看为是生产消费的数据,
    //同时也是判断当前该生产还是消费的标识
    private Integer num = 0;
    //2.创建 Lock 锁
    private Lock lock = new ReentrantLock();
    //3.创建对应 A生产的 Condition
    private Condition conditionA = lock.newCondition();
    //4.创建对应 B消费 Condition
    private Condition conditionB = lock.newCondition();
    //5.创建对应 C生产的 Condition
    private Condition conditionC = lock.newCondition();
    //6.创建对应 D消费 Condition
    private Condition conditionD = lock.newCondition();

    //A生产方法
    public void production() throws Exception {

        //1.生产时首先获取到锁
        lock.lock();
        try {

            //1.通过标识判断,当前是否生产,不可以则进入阻塞状态
            while (num != 0) {
                //相当于前面的 this.wait();
                conditionA.await();
            }
            //2.生产
            num++;
            System.out.println("A执行生产" + num);
            //3.生产完毕唤醒同一监视器下的其它线程(也就是唤醒消费线程进行消费)
            //相当于前面的 this.notify();
            conditionB.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //C生产方法
    public void productionC() throws Exception {

        //1.生产时首先获取到锁
        lock.lock();
        try {

            //1.通过标识判断,当前是否生产,不可以则进入阻塞状态
            while (num != 0) {
                //相当于前面的 this.wait();
                conditionC.await();
            }
            //2.生产
            num++;
            System.out.println("C执行生产" + num);
            //3.生产完毕唤醒同一监视器下的其它线程(也就是唤醒消费线程进行消费)
            //相当于前面的 this.notify();
            conditionD.signal();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


    //B消费方法
    public void consumerB() throws Exception {

        //1.消费时首先获取到锁
        lock.lock();
        try {
            //1.通过标识判断,当前是否消费,不可以则进入阻塞状态
            while (num == 0) {
                conditionB.await();
            }
            //2.消费
            num--;
            System.out.println("B执行消费" + num);
            //3.消费完毕,唤醒同一监视器下的其它线程(也就是唤醒生产线程进行生产)
            conditionA.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


    //D消费方法
    public void consumerD() throws Exception {

        //1.消费时首先获取到锁
        lock.lock();
        try {
            //1.通过标识判断,当前是否消费,不可以则进入阻塞状态
            while (num == 0) {
                conditionD.await();
            }
            //2.消费
            num--;
            System.out.println("D执行消费" + num);
            //3.消费完毕,唤醒同一监视器下的其它线程(也就是唤醒生产线程进行生产)
            conditionC.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  1. 多线程生产,消费运行测试
public class T1 {

    public static void main(String[] args) {
        MyResource resource = new MyResource();
        //1.使用匿名内部类方式创建 A 线程执行生产
        new Thread(() -> {
            try {
                for (int i = 0; i < 3; i++) {
                    resource.production();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        //2.使用匿名内部类方式创建 B 线程执行消费
        new Thread(() -> {
            try {
                for (int i = 0; i < 3; i++) {
                    resource.consumerB();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
        //3.使用匿名内部类方式创建 C 线程执行生产
        new Thread(() -> {
            try {
                for (int i = 0; i < 3; i++) {
                    resource.productionC();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "C").start();

        //4.使用匿名内部类方式创建 B 线程执行消费
        new Thread(() -> {
            try {
                for (int i = 0; i < 3; i++) {
                    resource.consumerD();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "D").start();
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程通信基础示例(synchronized 与 Lock + Condition实现线程通信) 的相关文章

  • 线程安全的单例模式

    线程安全的单例模式 单例模式 属于创建类型的一种常用的软件设计模式 通过单例模式创建的类在当前进程中只有一个实例 一份资源只能被申请加载一次 如何实现 饿汉模式 资源在程序初始化的时候就去加载 后边使用的时候直接使用 使用会非常流畅 但是有
  • 死锁产生条件和解决办法

    死锁 死锁产生的四个条件 产生死锁必须同时满足以下四个条件 只要其中任一条件不成立 死锁就不会发生 互斥条件 线程要求对所分配的资源 如打印机 进行排他性控制 即在一段时间内某资源仅为一个线程所占有 此时若有其他线程请求该资源 则请求线程只
  • Disruptor 详解

    Disruptor 详解 想了解一个项目 最好的办法就是 把它的源码搞到本地自己捣鼓 在网上看了 N 多人对 Disruptor 速度的吹捧 M 多人对它的机制分析 就连 Disruptor 官方文档中 也 NB 哄哄自诩 At LMAX
  • volatile和synchronized的区别

    共性 volatile与synchronized都用于保证多线程中数据的安全 区别 1 volatile修饰的变量 jvm每次都从主存 主内存 中读取 而不会从寄存器 工作内存 中读取 而synchronized则是锁住当前变量 同一时刻只
  • Java 线程关闭

    Java线程关闭的方式 1 使用状态位 public class CloseThread extends Thread boolean flag true int index 0 Override public void run while
  • Future 和 Callable

    一 Runnable 缺陷 不能返回一个返回值 不能抛出 checked Execption 二 Callable接口 类似于Runnable 被其他线程执行的任务 实现call方法 有返回值 三 Future的作用 Callable和Fu
  • java数据迁移程序

    环境 mysql 目标 亿级数据迁移 最终耗时 1 2小时 服务器更佳 建议在晚上或者没人访问的情况下操作 思路 1 不能一下将所有数据 导入到目标数据表 耗时太久 且占用资源 所有就用程序批量执行 每次执行一个范围段 比如第一个线程 1
  • cpu的出错概率?

    我今天想到了一个很不懂的问题 cpu执行指令会出错吗 出错的概率是多少 为什么服务器能够不间断的工作很长时间呢 难道cpu指令级的东西不会出错 操作系统怎么避免这些错误呢 2012 5 27 找到一篇文章 http wuyudong blo
  • WEB-7-多线程

    多线程 一 背景 二 认识线程 Thread 1 理解 2 进程和线程的区别 高频面试题 3 使用代码创建多线程 三 线程的相关操作 1 创建线程 1 方法一 继承 Thread 类 2 方法二 实现 Runnable 接口 3 其它方法
  • java中什么是并发,如何解决?

    多个进程或线程同时 或着说在同一段时间内 访问同一资源会产生并发问题 银行两操作员同时操作同一账户就是典型的例子 比如A B操作员同时读取一余额为1000元的账户 A操作员为该账户增加100元 B操作员同时为该账户减去 50元 A先提交 B
  • C++-std::unique_lock介绍和简单使用

    unique lock std unique lock比std lock guard更灵活 这种灵活性主要体现在以下几点 lock guard在构造时或者构造前 std adopt lock 就已经获取互斥锁 并且在作用域内保持获取锁的状态
  • Java 多线程模式 —— Guarded Suspension 模式

    Part1Guarded Suspension 模式的介绍 我们只从字面上看 Guarded Suspension 是受保护暂停的意思 1Guarded Suspension 模式 在实际的并发编程中 Guarded Suspension
  • 【Java】网络编程——多线程下载文件

    前言 多线程下载文件 比单线程要快 当然 线程不是越多越好 这和获取的源文件还有和网速有关 原理 在请求服务器的某个文件时 我们能得到这个文件的大小长度信息 我们就可以下载此长度的某一个片段 来达到多线程下载的目的 每条线程分别下载他们自己
  • JAVA使用线程池查询大批量数据

    前言 在开发过程中可能会碰到某些独特的业务 比如查询全部表数据 数据量过多会导致查询变得十分缓慢 虽然在大多数情况下并不需要查询所有的数据 而是通过分页或缓存的形式去减少或者避免这个问题 但是仍然存在需要这样的场景 比如需要导出所有的数据到
  • Linux系统编程:多线程交替打印ABC

    引言 分享关于线程的一道测试题 因为网上基本都是Java的解决方法 决定自己写一篇来记录一下线程的学习 问题描述 编写一个至少具有三个线程的程序 称之为线程 A B 和 C 其中线程 A 输出字符 A 线程 B 输出字符 B 线程 C 输出
  • Unity中实现倒计时的几种方式

    1 Time time using UnityEngine public class TimeTest MonoBehaviour public float secound 10 void Update Timing private flo
  • Java线程(Thread)生命周期的6种状态

    当线程被创建并启动以后 它既不是一启动就进入了执行状态 也不是一直处于执行状态 在线程的生命周期中 可能处于不同的状态 java lang Thread State 列举出了这6种线程状态 线程状态 导致状态发生条件 New 新建 线程刚被
  • 多线程下载文件(支持暂停、取消、断点续传)

    多线程下载文件 支持暂停 取消 断点续传 多线程同时下载文件即 在同一时间内通过多个线程对同一个请求地址发起多个请求 将需要下载的数据分割成多个部分 同时下载 每个线程只负责下载其中的一部分 最后将每一个线程下载的部分组装起来即可 涉及的知
  • 由一个多线程共享Integer类变量问题引起的。。。

    假设并发环境下 业务代码中存在一些统计操作 为了保证线程安全 开发人员往往会对计数值进行加锁 synchronized 值得注意的是 直接对Integer类型进行加锁 似乎并不会达到预期效果 比如下面这段代码 Integer num new
  • JUC的常见类

    目录 Callable ReentrantLock Semaphore CountDownLatch JUC 即 java util concurrent 其中存放了一些进行多线程编程时有用的类 Callable Callable是一个接口

随机推荐