在多线程编程中,临界区(Critical Section)指程序中可能因多个线程并发执行而导致结果异常的代码段。这种异常通常源于多个线程对共享资源的并发访问。Java语言本身不提供自动识别临界区的机制,但通过synchronized
关键字等构造,允许开发者显式声明临界区并控制线程访问。
典型临界区示例:
Java通过对象监视器(Monitor)实现线程同步,每个对象都关联一个内置监视器。监视器包含三个核心组件:
// 同步方法(实例方法)
public synchronized void updateAccount() {
// 临界区代码
}
// 同步方法(静态方法)
public static synchronized void audit() {
// 临界区代码
}
// 同步代码块
public void transfer() {
// 非临界区代码
synchronized(this) { // 显式指定监视器对象
// 临界区代码
}
}
确保同一时刻只有一个线程能执行临界区代码,通过独占锁实现。例如银行账户余额修改操作:
private int balance = 100;
public synchronized void updateBalance() {
balance += 10; // 存款
balance -= 10; // 取款
}
线程间通过条件变量协作,典型场景是生产者-消费者模式:
private Queue buffer = new LinkedList<>();
private final int CAPACITY = 1;
public synchronized void produce(Data data) throws InterruptedException {
while (buffer.size() == CAPACITY) {
wait(); // 缓冲区满时等待
}
buffer.add(data);
notifyAll(); // 唤醒消费者
}
public synchronized Data consume() throws InterruptedException {
while (buffer.isEmpty()) {
wait(); // 缓冲区空时等待
}
Data data = buffer.poll();
notifyAll(); // 唤醒生产者
return data;
}
wait()
释放锁并进入等待集notify()
随机唤醒一个等待线程,notifyAll()
唤醒所有等待线程wait()
调用必须放在循环中检查条件,防止虚假唤醒synchronized
,但内部可使用同步块// 错误1:局部变量作为锁对象
public void faultyMethod() {
Object lock = new Object(); // 每个线程创建新对象
synchronized(lock) { /* 实际无同步效果 */ }
}
// 错误2:if判断代替循环检查
public synchronized void consume() {
if (buffer.isEmpty()) { // 应该使用while
wait();
}
// 可能遇到条件不满足的情况
}
通过合理使用对象监视器机制,可以构建线程安全的高并发程序,但需要注意避免死锁、活锁等同步问题。后续章节将通过生产者-消费者等经典案例展示实际应用场景。
Java通过对象监视器实现同步方法,分为实例方法和静态方法两种形式:
public class Account {
private int balance;
// 同步实例方法
public synchronized void update(int amount) {
balance += amount; // 临界区代码
}
}
线程执行update()
方法前必须获取该Account对象实例的监视器锁。
public class Counter {
private static int count;
// 同步静态方法
public static synchronized void increment() {
count++; // 临界区代码
}
}
线程执行increment()
方法前需获取Counter.class对象的监视器锁。
同步代码块提供更精细的同步控制,支持任意对象作为监视器:
public class Transaction {
private final Object lock = new Object();
private List records;
public void addRecord(Record r) {
// 非同步代码
synchronized(lock) { // 显式指定锁对象
records.add(r); // 临界区代码
}
}
}
关键优势:
Java监视器锁支持可重入特性:
public class ReentrantDemo {
public synchronized void methodA() {
methodB(); // 可重入获取同一锁
}
public synchronized void methodB() {
// 已持有锁可直接进入
}
}
实现特点:
构造函数不能声明为synchronized,但可通过同步块实现同步:
public class SafeInit {
private static volatile SafeInit instance;
private final Map config;
private SafeInit() {
// 构造函数同步方案
synchronized(SafeInit.class) {
config = loadConfig(); // 线程安全的初始化
}
}
public static SafeInit getInstance() {
if (instance == null) {
synchronized(SafeInit.class) {
if (instance == null) {
instance = new SafeInit();
}
}
}
return instance;
}
}
public class LockReentry {
public synchronized void outer() {
inner(); // 锁计数器+1
}
public synchronized void inner() {
// 锁计数器再+1
}
} // 方法退出时锁计数器逐级递减
对象头结构:
性能优化:
内存语义:
正确使用synchronized需要平衡线程安全与性能,避免过度同步导致的性能下降。
Java对象监视器通过两个关键集合管理线程状态:
// 入口集示例
public class EntrySetDemo {
private final Object lock = new Object();
public void accessResource() {
synchronized(lock) { // 未获取锁的线程进入入口集等待
// 临界区代码
}
}
}
// 等待集示例
public class WaitSetDemo {
private boolean condition = false;
public synchronized void awaitCondition() throws InterruptedException {
while(!condition) {
wait(); // 当前线程进入等待集
}
// 条件满足后继续执行
}
}
JVM自动管理监视器锁的获取与释放,具体流程如下:
public class LockAcquisition {
public synchronized void methodA() {
methodB(); // 锁重入
}
public synchronized void methodB() {
// 同一线程可重复获取锁
}
public void methodC() {
synchronized(this) {
// 显式同步块获取锁
} // 自动释放锁
}
}
通过医疗场景类比可形象理解监视器工作机制:
医疗场景要素 | 对应监视器概念 | 技术说明 |
---|---|---|
诊室 | 监视器 | 同一时间只允许一个线程访问 |
初诊候诊区 | 入口集(Entry Set) | 等待获取锁的线程队列 |
检查等待区 | 等待集(Wait Set) | 调用wait()进入条件等待的线程 |
医生叫号系统 | notify()/notifyAll() | 条件满足时的线程唤醒机制 |
检查结果通知 | 条件变量 | 线程恢复执行的前提条件 |
新患者挂号(新线程请求锁)
检查中患者(条件等待线程)
synchronized(doctor) {
while(!pupilDilated) {
doctor.wait(); // 进入特殊等待区
}
// 继续检查...
}
检查完成通知(条件满足通知)
synchronized(doctor) {
pupilDilated = true;
doctor.notifyAll(); // 通知所有等待患者
}
等待机制规范
synchronized(lock) {
while(!condition) {
lock.wait();
}
// 处理业务逻辑
}
通知机制要点
错误防范措施
// 错误示例:使用局部变量作为锁
public void faultyMethod() {
Object localLock = new Object(); // 每个线程创建独立实例
synchronized(localLock) {
// 实际无同步效果
}
}
// 正确做法:使用共享final对象
private final Object sharedLock = new Object();
public void correctMethod() {
synchronized(sharedLock) {
// 有效的同步控制
}
}
监视器模型通过这种严谨的线程管理机制,在保证线程安全的同时,实现了高效的资源协调。理解其工作模型是编写正确并发程序的基础。
Java中的wait/notify机制必须遵循三个基本要素:
synchronized
方法或代码块内调用while
循环而非if
判断// 正确实现模板
public class ConditionWait {
private final Object lock = new Object();
private boolean ready = false;
public void await() throws InterruptedException {
synchronized(lock) {
while(!ready) { // 循环检测条件
lock.wait(); // 释放锁并等待
}
// 条件满足后执行操作
}
}
public void signal() {
synchronized(lock) {
ready = true;
lock.notifyAll(); // 唤醒所有等待线程
}
}
}
Java提供两种通知方式,具有本质区别:
方法 | 唤醒范围 | 适用场景 | 注意事项 |
---|---|---|---|
notify() |
随机唤醒单个 | 明确知道只需唤醒一个等待线程 | 可能产生"信号丢失"问题 |
notifyAll() |
唤醒全部 | 通用场景,特别是多条件等待情况 | 可能引起不必要的线程竞争 |
典型生产者-消费者实现:
public class BoundedBuffer {
private final Queue buffer = new LinkedList<>();
private final int capacity;
public BoundedBuffer(int capacity) {
this.capacity = capacity;
}
public synchronized void produce(int value) throws InterruptedException {
while(buffer.size() == capacity) {
wait(); // 缓冲区满时等待
}
buffer.add(value);
notifyAll(); // 唤醒所有消费者
}
public synchronized int consume() throws InterruptedException {
while(buffer.isEmpty()) {
wait(); // 缓冲区空时等待
}
int value = buffer.poll();
notifyAll(); // 唤醒所有生产者
return value;
}
}
虚假唤醒(Spurious Wakeup)指线程可能在没有收到通知的情况下被唤醒。防护措施:
while(conditionNotMet) {
wait(); // 唤醒后重新检查条件
}
// 使用原子状态标记
private AtomicBoolean processed = new AtomicBoolean(false);
public void process() throws InterruptedException {
synchronized(lock) {
while(!processed.get()) {
lock.wait();
}
}
}
public class TaskCoordinator {
private int completedTasks = 0;
private final int totalTasks;
public TaskCoordinator(int totalTasks) {
this.totalTasks = totalTasks;
}
public synchronized void taskCompleted() {
completedTasks++;
if(completedTasks == totalTasks) {
notifyAll(); // 所有任务完成时通知
}
}
public synchronized void awaitCompletion() throws InterruptedException {
while(completedTasks < totalTasks) {
wait(); // 等待所有任务完成
}
}
}
public class ConnectionPool {
private final List pool = new ArrayList<>();
private final int maxSize;
public ConnectionPool(int maxSize) {
this.maxSize = maxSize;
}
public synchronized Connection getConnection() throws InterruptedException {
while(pool.isEmpty()) {
wait(); // 等待可用连接
}
return pool.remove(0);
}
public synchronized void releaseConnection(Connection conn) {
pool.add(conn);
notifyAll(); // 通知等待线程
}
}
// 错误示例:不同步的锁对象
private Object lock1 = new Object();
private Object lock2 = new Object();
public void faultyMethod() {
synchronized(lock1) {
lock2.wait(); // 抛出IllegalMonitorStateException
}
}
public class NestedWait {
private final Object lock = new Object();
private boolean condition1 = false;
private boolean condition2 = false;
public void nestedWait() throws InterruptedException {
synchronized(lock) {
while(!condition1) {
lock.wait();
// 唤醒后需要检查所有相关条件
if(condition2) {
break;
}
}
}
}
}
public synchronized Result getResult(long timeout) throws InterruptedException {
long endTime = System.currentTimeMillis() + timeout;
while(!resultReady) {
long remaining = endTime - System.currentTimeMillis();
if(remaining <= 0) {
throw new TimeoutException();
}
wait(remaining); // 带超时的等待
}
return result;
}
通过合理运用wait/notify机制,可以构建高效的线程间协作模型,但必须严格遵循使用规范以避免竞态条件和死锁等问题。
通过同步方法解决多线程竞争问题,确保余额检查始终一致:
public class BalanceUpdateSynchronized {
private static int balance = 100;
public static synchronized void updateBalance() {
balance += 10; // 存款操作
balance -= 10; // 取款操作
}
public static synchronized void monitorBalance() {
if (balance != 100) {
System.out.println("余额异常: " + balance);
System.exit(1);
}
}
}
关键改进点:
static synchronized
声明方法,锁定类对象监视器当线程需要同时获取对象锁和类锁时,可能形成嵌套锁结构:
public class MultiLock {
public synchronized void instanceMethod() {
MultiLock.classMethod(); // 需要获取类锁
}
public static synchronized void classMethod() {
// 持有类锁时仍可获取实例锁
new MultiLock().innerMethod();
}
public synchronized void innerMethod() {
// 锁重入示例
}
}
执行特点:
局部变量锁失效问题:
public class InvalidLock {
public void process() {
Object lock = new Object(); // 每个线程创建独立锁对象
synchronized(lock) { // 实际无同步效果
// 临界区代码
}
}
}
修正方案:
public class ValidLock {
private final Object lock = new Object(); // 共享final锁对象
public void process() {
synchronized(lock) { // 有效的同步控制
// 临界区代码
}
}
}
Java提供三种wait()变体支持条件等待:
public class TimedWait {
private final Object condition = new Object();
private boolean ready = false;
public void await(long timeout) throws InterruptedException {
synchronized(condition) {
while(!ready) {
condition.wait(timeout); // 最大等待指定毫秒
if(!ready) {
throw new TimeoutException();
}
}
}
}
public void nanosAwait(long nanos) throws InterruptedException {
synchronized(condition) {
while(!ready) {
condition.wait(1000, (int)(nanos%1000000)); // 纳秒精度控制
}
}
}
}
注意事项:
策略 | 执行效果 | 适用场景 |
---|---|---|
notify() | 随机唤醒单个等待线程 | 明确知道只需唤醒一个消费者 |
notifyAll() | 唤醒所有等待线程 | 多生产者-消费者场景 |
超时wait() | 自动唤醒后检查条件 | 避免永久阻塞的系统保护 |
通过合理组合这些同步机制,可以构建健壮的并发程序,在保证线程安全的同时避免死锁和资源竞争问题。
Java线程同步的核心机制建立在对象监视器模型之上,通过synchronized
关键字和wait/notify
方法实现两种基本同步模式:
通过对象内置锁保证临界区原子性访问,JVM自动管理锁获取与释放过程:
// 互斥同步典型实现
public class Counter {
private int value;
public synchronized void increment() {
value++; // 原子性操作
}
}
使用wait/notify实现线程间条件协调,必须遵循"循环检测"模式:
// 条件同步标准范式
public class ConditionCoordinator {
private boolean ready = false;
public synchronized void await() throws InterruptedException {
while(!ready) { // 必须使用循环检查
wait(); // 释放锁并等待
}
}
public synchronized void signal() {
ready = true;
notifyAll(); // 唤醒所有等待线程
}
}
java.util.concurrent
包中的高级同步工具典型错误模式警示:
// 错误示例:不同步的复合操作
public class UnsafeCounter {
private int value;
public int getAndIncrement() {
return value++; // 非原子操作,需要同步
}
}
在复杂并发场景中,应优先考虑使用ReentrantLock
、CountDownLatch
等JUC组件,它们提供更灵活的同步控制和更好的性能特性。理解底层监视器机制是处理高级并发问题的基础,但实际开发中应当根据具体场景选择最合适的同步策略。