首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
当前位置:
首页>>
技术小册>>
Java并发编程实战
小册名称:Java并发编程实战
### 08 | 管程:并发编程的万能钥匙 在Java并发编程的广阔领域中,管程(Monitor)作为一种高级同步机制,扮演着至关重要的角色。它不仅是解决多线程间数据共享与互斥访问问题的有效手段,更是提升程序并发性能、简化并发控制逻辑的“万能钥匙”。本章将深入探讨管程的概念、原理、实现方式及其在Java中的应用,帮助读者掌握这一强大的并发编程工具。 #### 一、管程概述 **1.1 定义与起源** 管程(Monitor)这一概念最早由C.A.R. Hoare在1974年提出,旨在提供一种机制,使得多个线程能够安全地访问共享资源,同时避免复杂的同步控制代码。管程内部封装了共享数据和操作这些数据的函数(或称为方法),通过互斥锁(Mutex)保护数据,确保同一时刻只有一个线程能够执行管程内的代码。 **1.2 核心特性** - **互斥性**:管程内的任何时刻,至多只有一个线程能够执行其内部代码,保证了数据的一致性和完整性。 - **条件变量**:管程支持条件变量(Condition Variables),允许线程在特定条件未满足时挂起(阻塞),并在条件满足时被唤醒,从而有效管理线程间的等待/通知机制。 - **封装性**:管程将共享数据和操作这些数据的函数封装在一起,对外提供有限的接口,隐藏了内部实现细节,降低了系统的复杂性。 #### 二、管程的实现原理 **2.1 互斥锁的实现** 互斥锁是管程实现互斥性的基础。在Java中,`synchronized`关键字和`ReentrantLock`类是实现互斥锁的主要方式。`synchronized`可以修饰方法或代码块,自动管理锁的获取与释放;而`ReentrantLock`则提供了更灵活的锁操作,如尝试非阻塞地获取锁、可中断地获取锁、以及定时获取锁等。 **2.2 条件变量的实现** 条件变量是管程中用于线程间通信的关键组件。在Java中,`Object`类提供了`wait()`、`notify()`和`notifyAll()`方法,这些方法可以与`synchronized`关键字配合使用,实现条件变量的功能。然而,`ReentrantLock`类提供的`Condition`接口提供了更为丰富和灵活的条件变量操作,如支持多个条件变量、更精细的线程唤醒控制等。 **2.3 封装性的实现** 管程的封装性通过Java的类和方法访问控制(如`private`、`protected`、`public`等修饰符)来实现。将共享数据和操作这些数据的函数封装在同一个类中,并通过公共接口对外提供服务,可以有效隐藏内部实现细节,减少外部对共享资源的直接访问,从而降低出错概率。 #### 三、Java中的管程实现 **3.1 使用`synchronized`关键字** 在Java中,最简单的管程实现方式就是使用`synchronized`关键字。通过将方法或代码块标记为`synchronized`,可以自动实现互斥访问。然而,这种方式在处理复杂同步逻辑时可能显得力不从心,因为它不支持多个条件变量,且`wait()`和`notify()`方法的调用必须位于`synchronized`块内。 ```java public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } ``` **3.2 使用`ReentrantLock`和`Condition`** 为了克服`synchronized`的局限性,Java提供了`ReentrantLock`类和`Condition`接口,允许更灵活地实现管程。通过`ReentrantLock`可以显式地获取和释放锁,而`Condition`则提供了比`Object`的`wait()`/`notify()`/`notifyAll()`更丰富的条件变量操作。 ```java import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class BoundedBuffer<T> { private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); private final Object[] items = new Object[10]; private int putPos = 0; private int takePos = 0; private int count = 0; public void put(T x) throws InterruptedException { lock.lock(); try { while (count == items.length) { notFull.await(); } items[putPos] = x; if (++putPos == items.length) putPos = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public T take() throws InterruptedException { lock.lock(); try { while (count == 0) { notEmpty.await(); } T x = (T) items[takePos]; items[takePos] = null; if (++takePos == items.length) takePos = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } } ``` #### 四、管程的应用场景与优势 **4.1 应用场景** - **生产者-消费者问题**:管程是解决此类问题的天然工具,通过条件变量可以优雅地处理生产者和消费者之间的同步与通信。 - **读写锁**:利用管程的互斥性和条件变量,可以实现高效的读写锁,允许多个读操作并发执行,但写操作必须独占访问。 - **资源池管理**:如数据库连接池、线程池等,管程可以帮助管理资源的分配与回收,确保资源使用的安全性和高效性。 **4.2 优势** - **简化并发控制**:管程通过封装共享数据和操作,简化了并发控制逻辑,降低了出错概率。 - **提高性能**:通过减少不必要的锁竞争和等待时间,管程可以提高程序的并发性能。 - **增强可读性和维护性**:清晰的接口和封装性使得管程的实现更易于理解和维护。 #### 五、总结 管程作为并发编程中的“万能钥匙”,以其独特的互斥性、条件变量和封装性特性,在解决多线程同步与通信问题上展现出了强大的能力。在Java中,通过`synchronized`关键字和`ReentrantLock`/`Condition`组合,我们可以灵活地实现管程,以应对各种复杂的并发场景。掌握管程的原理和应用,对于提升Java并发编程能力具有重要意义。
上一篇:
07 | 安全性、活跃性以及性能问题
下一篇:
09 | Java线程(上):Java线程的生命周期
该分类下的相关小册推荐:
SpringBoot零基础到实战
SpringBoot合辑-初级篇
java源码学习笔记
Java语言基础9-常用API和常见算法
Java语言基础6-面向对象高级
Java语言基础12-网络编程
Java语言基础7-Java中的异常
Mybatis合辑2-Mybatis映射文件
Java必知必会-JDBC
JAVA 函数式编程入门与实践
Java语言基础15-单元测试和日志技术
Java语言基础10-Java中的集合