Java进阶篇--Condition与等待通知机制

news2024/12/28 21:22:20

Condition简介

Condition是Java并发包中的一种机制,用于线程之间的协作和通信。它与锁(Lock)紧密配合使用,并提供了更高级别的等待/通知功能。

下面是Condition的一些特性和区别:

1. 精确唤醒:Condition可以实现精确的线程唤醒机制。使用Object类的wait()方法时,只能通过notify()或notifyAll()方法唤醒等待的线程,无法指定具体哪个线程被唤醒。而使用Condition的signal()方法可以精确地唤醒一个等待线程,或者使用signalAll()方法唤醒所有等待线程。

2. 多条件支持:Condition可以支持多个条件队列。一个Lock对象可以关联多个Condition对象,每个Condition对象都可以管理一个独立的等待队列。线程可以选择在特定的条件下等待和唤醒,从而更加灵活地实现线程间的协作。

3. 等待超时:使用Condition时,可以指定线程等待的时间。调用await()方法时可以传入超时时间,在超过指定时间后线程会自动被唤醒,无需显式地调用notify()或notifyAll()方法。

4. 可中断等待:Condition支持线程中断。调用await()方法后,如果线程被中断,会立即抛出InterruptedException异常,可以在异常处理中进行相应的操作。

5. 可以替代synchronized关键字:使用Condition和Lock可以替代传统的使用synchronized关键字实现线程间通信的方式,更加灵活和可控。Condition的功能更强大,提供了更多的等待/通知机制。

总的来说,Condition接口提供了更高级别、更强大的线程等待和通知机制。它与Lock接口配合使用,能够满足更复杂的线程间通信需求,具有更高的可控性和扩展性。相比之下,Object类的wait/notify方法更低级,功能相对有限。

Condition实现原理

Condition是Java并发包中的一个重要组件,用于实现基于锁的等待/通知机制。Condition的实现原理分析主要涉及等待队列、await()方法和signal/signalAll()方法三个方面。

1. 等待队列

Condition依赖于底层的锁机制来实现线程同步和互斥,而等待队列则是Condition用于管理等待线程的一种数据结构。在Lock对象中维护了一个等待队列来管理所有等待在该锁上的线程。

等待队列是一个FIFO(先进先出)的队列,其中每个节点代表了一个等待线程。当一个线程调用Condition的await()方法时,它会释放持有的锁并进入等待状态,同时将当前线程插入到等待队列的末尾。而当其他线程调用Condition的signal()或signalAll()方法时,会从等待队列的头部取出一个节点,并将其转移到锁的同步队列中。

2. await()方法实现原理

await()方法是Condition中最核心的方法之一,它用于将当前线程设置为等待状态,并加入到等待队列中。await()方法的具体实现流程如下:

(1) 获得Lock对象。

(2) 将线程状态设置为WAITING。

(3) 将当前线程加入到等待队列的尾部。

(4) 释放锁。

(5) 等待被唤醒。

当await()方法被执行后,线程会释放持有的锁,并进入等待状态。同时,当前线程会插入到Lock对象的等待队列中,等待其他线程调用signal()或signalAll()方法唤醒它。

3. signal/signalAll()方法实现原理

signal()和signalAll()方法是Condition中用于唤醒等待线程的方法。当锁的状态发生变化后,需要唤醒一个或多个等待线程来执行特定的操作,就可以调用这些方法。signal()方法通常只唤醒一个等待线程,而signalAll()方法则会唤醒所有等待线程。

signal/signalAll()方法的具体实现流程如下:

(1) 获得Lock对象。

(2) 从等待队列中取出头部节点。

(3) 将头部节点转移到Lock对象的同步队列中。

(4) 唤醒被转移节点的线程。

(5) 释放锁。

在执行signal/signalAll()方法时,需要获取Lock对象并从等待队列中取出头部节点。然后将头部节点转移到同步队列中,并唤醒该节点对应的线程。最后释放锁,使得其他线程可以继续竞争锁。

总的来说,Condition的实现依赖于底层的锁机制和等待队列,通过等待队列实现了线程的等待和唤醒,通过锁机制实现了线程同步和互斥。当一个线程调用await()方法时,会释放锁并进入等待状态,并且将当前线程加入到等待队列中。而当其他线程调用signal()或signalAll()方法时,会从等待队列中取出一个节点,并将其转移到同步队列中,并唤醒对应的线程。这样就可以实现线程之间的协作和通信,提供更高级别、更灵活的等待/通知机制。

代码示例

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

public class main {
    private final Lock lock = new ReentrantLock(); // 创建锁对象
    private final Condition notFull = lock.newCondition(); // 非满条件
    private final Condition notEmpty = lock.newCondition(); // 非空条件
    private final Queue<Integer> buffer = new LinkedList<>(); // 缓冲区
    private final int capacity = 5; // 缓冲区容量

    public void produce() throws InterruptedException {
        while (true) {
            lock.lock(); // 获取锁
            try {
                while (buffer.size() == capacity) { // 如果缓冲区满了,等待非满条件
                    notFull.await();
                }
                int item = produceItem(); // 生产物品
                buffer.offer(item); // 放入缓冲区
                System.out.println("生产: " + item);
                notEmpty.signal(); // 唤醒等待非空条件的消费者线程
            } finally {
                lock.unlock(); // 释放锁
            }
            Thread.sleep(1000); // 生产物品的时间间隔
        }
    }

    public void consume() throws InterruptedException {
        while (true) {
            lock.lock(); // 获取锁
            try {
                while (buffer.isEmpty()) { // 如果缓冲区空了,等待非空条件
                    notEmpty.await();
                }
                int item = buffer.poll(); // 从缓冲区取出物品
                System.out.println("已消耗: " + item);
                notFull.signal(); // 唤醒等待非满条件的生产者线程
            } finally {
                lock.unlock(); // 释放锁
            }
            Thread.sleep(1000); // 消费物品的时间间隔
        }
    }

    private int produceItem() {
        return (int) (Math.random() * 100); // 生产一个随机数作为物品
    }

    public static void main(String[] args) {
        main producerConsumer = new main();
        new Thread(() -> {
            try {
                producerConsumer.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                producerConsumer.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

await与signal/signalAll的结合思考

await和signal/signalAll是Condition接口定义的方法,用于线程之间的协作和通信。下面是一些思考:

1、await方法的作用是什么?

await方法用于使当前线程等待,直到某个条件为真。当线程调用await方法后,它会释放锁,并进入等待状态,直到其他线程通过signal或signalAll方法将其唤醒。

2、signal方法的作用是什么?

signal方法用于唤醒一个等待中的线程。当某个条件发生变化时,可以调用signal方法来选择唤醒其中一个等待的线程。被唤醒的线程会尝试重新获得锁,并继续执行。

3、signalAll方法的作用是什么?

signalAll方法用于唤醒所有等待中的线程。当某个条件发生变化时,可以调用signalAll方法来唤醒所有等待的线程。被唤醒的线程会尝试重新获得锁,并继续执行。

4、为什么需要使用await和signal/signalAll来实现线程协作和通信?

  • 使用锁和条件的方式可以实现更加精细的线程协作和通信。通过await方法,线程可以主动释放锁,并等待某个条件的发生;
  • 而通过signal/signalAll方法,线程可以选择性地唤醒等待的线程,从而实现更加灵活的线程调度和通信。

5、怎么选择使用signal还是signalAll方法?

  • 如果只有一个线程在等待某个条件,那么使用signal方法来唤醒等待的线程即可。
  • 如果有多个线程在等待某个条件,而且需要同时唤醒它们,那么使用signalAll方法来唤醒所有等待的线程。

6、使用await和signal/signalAll方法需要注意什么?

  • 在使用await方法前,必须先获得锁。否则会抛出IllegalMonitorStateException异常。
  • await方法被调用后,当前线程会释放锁,并进入等待状态,直到其他线程调用signal或signalAll方法来唤醒它。
  • 唤醒等待的线程后,被唤醒的线程会尝试重新获得锁,但不一定立即成功,需要竞争锁资源,可能会有其他线程先获取到锁并执行。
  • 在使用signal/signalAll方法时,应该确保在调用这些方法之前已经改变了相应的条件,否则可能导致等待的线程无法满足条件而继续等待。

这种等待/通知机制的典型应用场景是生产者与消费者问题。生产者线程通过获取锁并进入等待状态,直到有消费者线程进行通知唤醒。消费者线程在消费完数据后,再次获取锁并通知等待中的生产者线程。这样可以实现生产者与消费者之间的协同工作,避免了无效的轮询和资源浪费。

需要注意的是,在使用await和signal/signalAll时,应该确保在调用这些方法之前已经改变了相应的条件,这样等待的线程才能满足条件而被唤醒。

总的来说,await和signal/signalAll是一种强大的线程协作和通信机制,在多线程编程中可以帮助我们实现高效的线程调度和通信模式。合理使用这些方法可以避免死锁、提高程序的性能和可靠性。

代码示例

以下是一个简单的示例代码,演示了如何使用await和signal方法实现线程之间的等待和通知机制,以解决生产者与消费者问题。

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

public class main {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Queue<Integer> queue = new LinkedList<>();
    private final int MAX_SIZE = 10;

    public void produce() throws InterruptedException {
        lock.lock(); // 获取锁
        try {
            while (queue.size() == MAX_SIZE) {
                // 队列已满,生产者进入等待状态
                notFull.await();
            }
            // 生产数据
            int number = getNextNumber();
            queue.offer(number);
            System.out.println("生产: " + number);

            // 唤醒一个等待中的消费者线程
            notEmpty.signal();
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public void consume() throws InterruptedException {
        lock.lock(); // 获取锁
        try {
            while (queue.isEmpty()) {
                // 队列为空,消费者进入等待状态
                notEmpty.await();
            }
            // 消费数据
            int number = queue.poll();
            System.out.println("已消耗: " + number);

            // 唤醒一个等待中的生产者线程
            notFull.signal();
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    private int getNextNumber() {
        // 模拟生成数据的过程
        return (int) (Math.random() * 100);
    }

    public static void main(String[] args) {
        main example = new main();

        // 创建生产者线程
        Thread producerThread = new Thread(() -> {
            try {
                while (true) {
                    example.produce();
                    Thread.sleep(1000); // 生产一个数据后休眠1秒
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 创建消费者线程
        Thread consumerThread = new Thread(() -> {
            try {
                while (true) {
                    example.consume();
                    Thread.sleep(2000); // 消费一个数据后休眠2秒
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 启动线程
        producerThread.start();
        consumerThread.start();
    }
}

在这个例子中,我们使用了Lock和Condition对象来实现等待/通知机制。生产者线程通过调用notFull.await()方法进入等待状态,直到队列不满时被唤醒;消费者线程通过调用notEmpty.await()方法进入等待状态,直到队列非空时被唤醒。

注意,在生产者生产数据后,需要调用notEmpty.signal()方法来唤醒一个等待中的消费者线程;在消费者消费数据后,需要调用notFull.signal()方法来唤醒一个等待中的生产者线程。这样就实现了生产者与消费者的协作工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1123272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Spring篇】数据源对象管理加载properties文件

&#x1f38a;专栏【Spring】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 &#x1f970;欢迎并且感谢大家指出小吉的问题 文章目录 &#x1f33a;数据源对象管理&#x1f6f8;基础版⭐在pom.xml文件中加入下面的代码&…

HVV(护网)蓝队视角的技战法分析

一、背景 1.HVV行动简介 HVV行动是国家应对网络安全问题所做的重要布局之一。从2016年开始&#xff0c;随着我国对网络安全的重视&#xff0c;演习规模不断扩大&#xff0c;越来越多的单位都加入到HVV行动中&#xff0c;网络安全对抗演练越来越贴近实际情况&#xff0c;各机构…

C++对象模型(20)-- 函数语义学:函数和变量的绑定问题

1、静态类型和动态类型 静态类型&#xff1a;对象定义时的类型&#xff0c;编译期间就确定好的。定义的时候是什么就是什么。 动态类型&#xff1a;对象目前所指向的类型&#xff0c;运行时才确定的类型。一般只有指针和引用才有动态类型。 比如下面的代码&#xff1a; cla…

SpringBoot+Vue实现AOP系统日志功能

AOP扫盲&#xff1a;Spring AOP (面向切面编程&#xff09;原理与代理模式—实例演示 logs表&#xff1a; CREATE TABLE logs (id int(11) NOT NULL AUTO_INCREMENT,operation varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 操作名称,type varchar(255) COLL…

博途S7-1200PLC自由口通信(Send_P2P和Receive_P2P指令编程)

S7-1200PLC的MODBUS-RTU通信的实战应用和完整SCL源代码,请参看下面的文章链接 https://rxxw-control.blog.csdn.net/article/details/132845221https://rxxw-control.blog.csdn.net/article/details/132845221MODBUS-RTU协议和常用功能码解读 https://rxxw-control.blog.csd…

【精选】自学网络安全的三个必经阶段(含路线图)

一、为什么选择网络安全&#xff1f; 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前踏入…

[java进阶]——多线程Thread类,处理并发异常的方法

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 一、理解进程与线程 二、Thread类 三、自定义线程的三种实现方式 四、多线程应用场景 五、解决并发问题的方法 5.1 synchronized()关键字 - 同步代码块 5.2使用lock锁 一、理解进程与线程 运行一个程序占用…

VS Code设置代码自动保存

给新电脑安了VS Code&#xff0c;提交运行代码前总是忘了保存&#xff0c;之前的电脑里是设置了自动保存按钮&#xff0c;所以导致我在新电脑上总是忘了。特记录VS Code设置自动保存功能。 首先在右下角找到“设置”按钮 然后在输入框输入“auto save”进行查找 可以看到自动…

Centos磁盘爆满_openEuler系统磁盘爆满清理方法---Linux工作笔记060

磁盘爆满,监控部门就会报警,报警就要处理,但是程序员并不擅长做运维的工作,记录一下把...以后用到会方便: 使用df -h命令可以看到,对应的磁盘占用情况,这里我的/dev/mapper/openeuler-root这个目录 占用的磁盘比较多,到了百分之95了.. 往往就是这个跟目录,我这里/data目录是自…

CSDN提供的Markdown常用模板

标题 新的改变 我们对Markdown编辑器进行了一些功能拓展与语法支持&#xff0c;除了标准的Markdown编辑器功能&#xff0c;我们增加了如下几点新功能&#xff0c;帮助你用它写博客&#xff1a; 全新的界面设计 &#xff0c;将会带来全新的写作体验&#xff1b;在创作中心设置…

1024程序员节特辑:【Spring Boot自动配置原理揭秘】

自动配置原理 概述原理Spring Boot Starterspring.factories 文件ConditionalOnX 注解配置 Bean配置属性 源码剖析复合AnnotationEnableAutoConfigurationAutoConfigurationImportSelector 主页传送门&#xff1a;&#x1f4c0; 传送 概述 Spring Boot 是一个用于创建独立的、…

.rancher-pipeline.yml

一、注意点 其实下文二的image是基于这个镜像作为基础镜像在这个镜像中执行打包&#xff0c;shellScript 当前路径是你代码块与上图settings.xml&#xff0c;图中的settings.xml可以替换下你当前镜像的settings.xml 示例 二、.rancher-pipeline.yml ${CICD_GIT_BRANCH}这些从官…

天下苦定制久矣,平台化建设到底难在哪里?

为什么需要平台 随着业务的不断发展&#xff0c;软件系统不可避免的走向熵增&#xff1a;复杂度越来越高、研发效率越来越差、稳定性逐渐降低等。这时抽象核心能力&#xff0c;走向平台化的道路成为很多系统的首要选择 平台的建设目标 产品的核心价值在于其有效性和用户体验…

视频号视频如何下载(WeChatVideoDownloader)

背景介绍 最近需要一个视频号里面的视频进行宣传用&#xff0c;网上找了很多方法都不行&#xff0c;特别是下载抓包工具Fiddler&#xff0c;然后监控HTTPS请求的&#xff0c;截取URL把URL中20302改成20304&#xff0c;再用IDM工具下载对应的资源&#xff0c;最后修改后缀名.mp…

《红蓝攻防对抗实战》三.内网探测协议出网之HTTP/HTTPS协议探测出网

目录 一. 在 Windows 操作系统中探测 HTTP/HTTPS 出网 1. Bitsadmin 命令 2.Certuil 命令 2.Linux系统探测HTTP/HTTPS出网 1.Curl命令 2.Wget命令 对目标服务器探测 HTTP/HTTPS 是否出网时&#xff0c;要根据目标系统类型执行命令&#xff0c;不同类型的操作系统使用的探…

GeoServer改造Springboot源码一(公共部分)

今天开始开启关于GeoServer的一个全新系列,主要是把改造Springboot后的详细代码粘贴出来,此文应配合《GeoServer改造Springboot启动》系列共同阅读,按照前系列的时间顺序结合此系列的源码展示可以快速构建GeoServer功能的二次封装的后端系统。 一、Springboot部分源码结构 …

NC资金管理相关问题

1、差旅费借款单支付单位是另外一家公司&#xff0c;审批后自动签字&#xff0c;审批时报错是为什么&#xff1f;报错信息是&#xff1a;没有当前资源的操作权限。 答&#xff1a;用户没有结算节点支付单位的权限&#xff0c;所以当无权限进行自动签字。 2、收付款单在结算节点…

如何破解压缩包密码,CTF压缩包处理

I. 引言 压缩包我们经常接触&#xff0c;用于对文件进行压缩存储/传输。压缩包处理在CTF比赛中是非常重要的一块&#xff0c;因为压缩包中可能包含重要信息&#xff1a;许多CTF题目会将关键信息隐藏在压缩包中&#xff0c;参赛者需要解压并查看其中的内容才能获取有用的线索。…

自动巡查、自动换充电……浙江这两台无人机“巢穴”派大用场

浙江省积极探索利用高科技的无人机技术提高森林防火效率。在杭州市西湖区的西山国家森林公园和绍兴市柯桥区的大香林风景区&#xff0c;部署了两台复亚智能全自动无人机飞行系统&#xff0c;实现了火情的自动检测、定期自动巡查以及迅速响应。该技术的应用标志着杭州从传统的“…

设计院图纸加密防泄密方案——天锐绿盾加密软件@德人合科技

天锐绿盾是一款专业的企业信息化防泄密软件&#xff0c;主要针对文档全生命周期进行加密保护&#xff0c;包括创建、修改、传输、归档、分发、销毁等全过程。它可以加强外发数据及终端离线的管理&#xff0c;对正常授权外带范围内的数据流程进行规范。设计图纸、文档等成果数据…