Java 显式锁与 Condition 的使用详解

news2025/5/14 2:51:23

Java 显式锁与 Condition 的使用详解

在多线程编程中,线程间的协作与同步是核心问题。Java 提供了多种机制来实现线程同步,除了传统的 synchronized 关键字外,ReentrantLockCondition 是更灵活且功能强大的替代方案。本文将详细介绍显式锁与 Condition 的使用,并通过实际案例分析其工作原理及常见误区。


一、显式锁与 Condition 简介

1. 显式锁(ReentrantLock)

ReentrantLock 是 Java 提供的可重入互斥锁,它比 synchronized 提供了更高的灵活性:

  • 支持尝试加锁(tryLock())。
  • 支持超时等待。
  • 支持公平锁与非公平锁。
  • 可以绑定多个 Condition 条件变量。

2. Condition 条件变量

Condition 是与 ReentrantLock 绑定的条件队列,用于实现线程的等待和唤醒。它替代了传统的 Object.wait()Object.notify(),并且支持为不同的条件定义独立的等待队列,从而更精确地控制线程协作。


二、典型使用场景:生产者-消费者模型

以下是一个使用 ReentrantLockCondition 实现的**有界缓冲区(Bounded Buffer)**示例:

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

public class BoundedBuffer<T> {
    private final List<T> buffer;
    private final int capacity;
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    public BoundedBuffer(int capacity) {
        this.capacity = capacity;
        this.buffer = new ArrayList<>();
    }

    public void put(T item) throws InterruptedException {
        lock.lock();
        try {
            while (buffer.size() == capacity) {
                notFull.await(); // 缓冲区满时等待
            }
            buffer.add(item);
            notEmpty.signal(); // 通知消费者缓冲区非空
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (buffer.isEmpty()) {
                notEmpty.await(); // 缓冲区空时等待
            }
            T item = buffer.remove(0);
            notFull.signal(); // 通知生产者缓冲区有空间
            return item;
        } finally {
            lock.unlock();
        }
    }
}

主程序:启动生产者与消费者线程

public class ProducerConsumerExample {
    public static void main(String[] args) {
        BoundedBuffer<Integer> buffer = new BoundedBuffer<>(10);

        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    buffer.put(i);
                    System.out.println("Produced: " + i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    Integer item = buffer.take();
                    System.out.println("Consumed: " + item);
                    Thread.sleep(150);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();
    }
}

示例说明

  • ReentrantLock:通过 lock.lock()lock.unlock() 控制对共享资源的访问。
  • ConditionnotFullnotEmpty 分别用于生产者和消费者线程的等待与唤醒。
  • while 循环:用于防止虚假唤醒(Spurious Wakeup),确保条件满足后再继续执行。

三、常见误区:不加锁调用 Condition.await()

❌ 错误示例

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

// 未加锁直接调用 await()
condition.await(); // 抛出 IllegalMonitorStateException

✅ 正确做法

必须在持有锁的情况下调用 await()

lock.lock();
try {
    while (!conditionMet) {
        condition.await(); // 必须持有锁
    }
} finally {
    lock.unlock();
}

🔒 为什么必须加锁?

  1. 原子性释放锁与等待

    • await() 的设计要求线程在调用时持有锁,以便在等待前释放锁,防止死锁。
    • 如果未持有锁,线程无法确定要释放哪个锁,导致逻辑混乱。
  2. Java 的强制性检查

    • Condition 内部会检查当前线程是否持有锁。若未持有,会抛出 IllegalMonitorStateException
  3. 与 Object.wait() 的类比

    • Object.wait() 必须在 synchronized 块中调用。
    • Condition.await() 必须在 ReentrantLock.lock() 保护的代码块中调用。

四、最佳实践

  1. 始终使用 while 而不是 if

    • 防止虚假唤醒(Spurious Wakeup),确保条件再次检查。
  2. 在 finally 块中释放锁

    • 避免因异常导致锁未释放,造成死锁。
  3. 选择 signal 或 signalAll

    • signal() 唤醒单个线程,适用于单一条件满足的场景。
    • signalAll() 唤醒所有等待线程,适用于复杂条件变化的场景。
  4. 避免在无锁状态下操作 Condition

    • 所有 await()signal() 操作必须在持有锁的上下文中执行。

五、总结

ReentrantLockCondition 是 Java 多线程编程中强大的工具,它们提供了比 synchronized 更细粒度的线程控制能力。通过合理使用锁和条件变量,可以高效实现线程协作,避免竞态条件和死锁问题。但需注意:

  • 必须在持有锁的情况下调用 await()signal()
  • 使用 while 循环检查条件
  • 始终在 finally 块中释放锁

掌握这些原则,能够帮助开发者构建出安全、高效的并发程序。

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

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

相关文章

#Redis黑马点评#(五)Redisson原理详解

目录 一 基于Redis的分布式锁优化 二 Redisson 1 实现步骤 2 Redisson可重入锁机制 3 Redisson可重试机制 4 Redisson超时释放机制 5 RedissonMultiLock解决主从一致性 三 trylock与lock两者有何区别 四 Redis优化秒杀 一 基于Redis的分布式锁优化 二 Redisson Redis…

23.(vue3.x+vite)引入组件并动态切换(component)

让多个组件使用同一个挂载点,并动态切换,这就是动态组件 效果截图 A组件代码: <template><div><div>{{message }}</</

VBA会被Python代替吗

VBA不会完全被Python取代、但Python在自动化、数据分析与跨平台开发等方面的优势使其越来越受欢迎、两者将长期并存且各具优势。 Python以其易于学习的语法、强大的开源生态系统和跨平台支持&#xff0c;逐渐成为自动化和数据分析领域的主流工具。然而&#xff0c;VBA依旧在Exc…

SEMI E40-0200 STANDARD FOR PROCESSING MANAGEMENT(加工管理标准)-(三)完结

10 消息服务详情 10.1 本章定义实现加工管理概念所需的消息服务。这些消息已在第8.1节中初步介绍。 协议无关性&#xff1a;这些服务独立于所使用的消息协议&#xff0c;可映射至SECS-II&#xff08;SEMI E5&#xff09;或其他类似协议。 10.1.1 消息服务定义内容包括&#…

MySQL数据库创建、删除、修改

一&#xff1a;建库建表 我们以学校体系进行建表。将数据库命名为school。 以下代码中的大写均可小写不影响。如CREATE DATABASE与create database相同 四个关键的实体分别是学院、老师、学生和课程&#xff0c;其中&#xff0c;学生跟学院是从属关系&#xff0c;这个关系从…

【氮化镓】GaN在不同电子能量损失的SHI辐射下的损伤

该文的主要发现和结论如下: GaN的再结晶特性 :GaN在离子撞击区域具有较高的再结晶倾向,这导致其形成永久损伤的阈值较高。在所有研究的电子能量损失 regime 下,GaN都表现出这种倾向,但在电子能量损失增加时,其效率会降低,尤其是在材料发生解离并形成N₂气泡时。 能量损失…

防火墙来回路径不一致导致的业务异常

案例拓扑&#xff1a; 拓扑描述&#xff1a; 服务器有2块网卡&#xff0c;内网网卡2.2.2.1/24 网关2.2.254 提供内网用户访问&#xff1b; 外网网卡1.1.1.1/24&#xff0c;外网网关1.1.1.254 80端口映射到公网 这个时候服务器有2条默认路由&#xff0c;分布是0.0.0.0 0.0.0.0 1…

WTK6900C-48L:离线语音芯片重构玩具DNA,从“按键操控”到“声控陪伴”的交互跃迁

一&#xff1a;开发背景 随着消费升级和AI技术进步&#xff0c;传统玩具的机械式互动已难以满足市场需求。语音控制芯片的引入使玩具实现了从被动玩耍到智能交互的跨越式发展。通过集成高性价比的语音识别芯片&#xff0c;现代智能玩具不仅能精准响应儿童指令&#xff0c;还能实…

Python 数据分析与可视化:开启数据洞察之旅(5/10)

一、Python 数据分析与可视化简介 在当今数字化时代&#xff0c;数据就像一座蕴藏无限价值的宝藏&#xff0c;等待着我们去挖掘和探索。而 Python&#xff0c;作为数据科学领域的明星语言&#xff0c;凭借其丰富的库和强大的功能&#xff0c;成为了开启这座宝藏的关键钥匙&…

gitkraken 使用教程

一、安装教程 安装6.5.3&#xff0c;之后是收费的&#xff0c;Windows版免安装 二、使用教程 0. 软件说明 gitkraken是一个git本地仓库管理软件&#xff0c;可以管理多个仓库&#xff0c;并且仓库可以属于多个网站多个账户。 1. 克隆仓库 选择要克隆到什么位置&#xff0…

【LeetCode 热题 100】二叉树 系列

&#x1f4c1; 104. 二叉树的最大深度 深度就是树的高度&#xff0c;即只要左右子树其中有一个不为空&#xff0c;就继续往下递归&#xff0c;知道节点为空&#xff0c;向上返回。 int maxDepth(TreeNode* root) {if(root nullptr)return 0;return max(maxDepth(root->lef…

用drawdb.app可视化创建mysql关系表

平时自己建表,没有可视化图形参考 为了便于理解,用drwadb画mysql关系表 drawDB | Online database diagram editor and SQL generator

火绒互联网安全软件:自主引擎,精准防御

在数字时代&#xff0c;网络安全是每一个用户都必须重视的问题。无论是个人用户还是企业用户&#xff0c;都需要一款高效、可靠的反病毒软件来保护设备免受恶意软件的侵害。今天&#xff0c;我们要介绍的 火绒互联网安全软件&#xff0c;就是这样一款由资深工程师主导研发并拥有…

【前端基础】8、CSS的选择器

一、什么是选择器&#xff1f; 根据一定的规则选出符合条件的HTML元素&#xff0c;从而为他们添加各种特定的样式。 二、选择器分类 通用选择器元素选择器类选择器id选择器属性选择器后代选择器兄弟选择器选择器组伪类 三、通用选择器&#xff08;*&#xff09; 作用&…

Gitee Team:关键领域行业DevSecOps落地的项目管理引擎

在全球数字化转型浪潮下&#xff0c;关键领域行业的软件研发正面临前所未有的挑战与机遇。国产化进程的加速推进与国防装备的智能化转型&#xff0c;对软件研发效能和质量提出了更高要求。在这样的背景下&#xff0c;Gitee Team作为国内领先的研发协作平台&#xff0c;正在为关…

网址为 http://xxx:xxxx/的网页可能暂时无法连接,或者它已永久性地移动到了新网址

这是由于浏览器默认的非安全端口所导致的&#xff0c;所谓非安全端口&#xff0c;就是浏览器出于安全问题&#xff0c;会禁止一些网络浏览向外的端口。 避免使用6000,6666这样的端口 6000-7000有很多都不行&#xff0c;所以尽量避免使用这个区间 还有在云服务器中&#xff0c…

鸿蒙跨平台开发教程之Uniapp布局基础

前两天的文章内容对uniapp开发鸿蒙应用做了一些详细的介绍&#xff0c;包括配置开发环境和项目结构目录解读&#xff0c;今天我们正式开始写代码。 入门新的开发语言往往从Hello World开始&#xff0c;Uniapp的初始化项目中已经写好了一个简单的demo&#xff0c;这里就不再赘述…

uniapp使用npm下载

uniapp的项目在使用HBuilder X创建时是不会有node_modules文件夹的&#xff0c;如下图所示&#xff1a; 但是uni-app不管基于哪个框架&#xff0c;它内部一定是有node.js的&#xff0c;否则没有办法去实现框架层面的一些东西&#xff0c;只是说它略微有点差异。具体差异表现在…

C# 的异步任务中, 如何暂停, 继续,停止任务

namespace taskTest {using System;using System.Threading;using System.Threading.Tasks;public class MyService{private Task? workTask;private readonly SemaphoreSlim semaphore new SemaphoreSlim(0, 1); // 初始为 0&#xff0c;Start() 启动时手动放行private read…

2025年AI工程师认证深度解析:AAIA认证体系全景指南与实战策略

一、IAAAI认证体系演进与价值定位 1.1 国际人工智能认证发展现状 全球人工智能认证市场呈现显著分化态势。据Gartner 2025Q1报告显示&#xff0c;北美市场以IEEE/ACM双认证体系为主导&#xff08;市占率38%&#xff09;&#xff0c;欧盟区推行AI Act合规认证&#xff08;强制…