Java 锁机制详解
锁是Java并发编程中实现线程同步的核心机制,理解各种锁的特性和使用场景对于构建高性能的并发应用至关重要。本文将详细介绍Java中的各种锁机制。
本文内容概览
核心价值
Java锁机制 = 线程同步 + 资源保护 + 执行顺序控制 + 内存可见性
- 🔒 同步访问:确保共享资源的互斥访问
- 🛡️ 数据保护:避免数据竞争和状态不一致
- ⚡ 高性能:通过不同锁策略优化并发性能
- 🔄 执行控制:管理线程的执行顺序和时序关系
- 👁️ 可见性:确保线程间数据修改的可见性
1. 锁机制概述
1.1 什么是锁?
核心概念
锁是一种同步机制,用于控制多个线程对共享资源的访问。锁确保在任意时刻只有一个线程能够访问被保护的资源,从而保证数据的一致性和完整性。
1.2 锁的分类
| 分类维度 | 类型 | 特点 | 适用场景 |
|---|---|---|---|
| 实现方式 | synchronized | 内置锁,自动管理 | 简单同步需求 |
| Lock接口 | 显式锁,手动管理 | 复杂同步需求 | |
| 公平性 | 非公平锁 | 性能高,不保证顺序 | 一般场景 |
| 公平锁 | 保证FIFO顺序 | 需要公平性场景 | |
| 可重入性 | 可重入锁 | 同一线程可多次获取 | 递归调用场景 |
| 不可重入锁 | 同一线程只能获取一次 | 简单场景 | |
| 读写特性 | 独占锁 | 只允许一个线程访问 | 写操作 |
| 读写锁 | 允许多个读,单个写 | 读多写少场景 | |
| 乐观/悲观 | 悲观锁 | 假设会发生冲突,先获取锁 | 高并发写操作 |
| 乐观锁 | 假设不会发生冲突,检测更新 | 读多写少场景 |
锁分类详细图
1.3 锁的性能特性
- 锁类型性能对比
- 关键性能指标
- 最佳场景
| 锁类型 | 性能 | 功能 | 使用复杂度 | 死锁风险 | 活跃性 |
|---|---|---|---|---|---|
| synchronized | 中等 | 基础功能 | 简单 | 中等 | 不可中断 |
| ReentrantLock | 高 | 丰富 | 中等 | 低(可超时) | 可中断 |
| ReadWriteLock | 读多写少场景高 | 分离读写 | 中等 | 中等 | 可中断 |
| StampedLock | 非常高 | 乐观读 | 复杂 | 低 | 高 |
| 乐观锁(CAS) | 低竞争下高 | 无阻塞 | 复杂 | 无 | 高 |
锁的性能衡量指标:
- 锁竞争开销:多个线程同时请求锁时的性能损失
- 锁持有时间:获取锁到释放锁的平均时间
- 等待队列长度:等待获取锁的线程数量
- 上下文切换次数:线程因为锁而被挂起和恢复的次数
- 内存开销:锁机制所需的内存空间
- CPU利用率:锁实现机制对CPU的使用率
- 可伸缩性:随着线程数增加,性能的变化趋势
| 场景 | 推荐锁 | 原因 |
|---|---|---|
| 简单同步 | synchronized | 简单易用,自动管理 |
| 需要中断/超时 | ReentrantLock | 支持lockInterruptibly()和tryLock() |
| 读多写少 | ReadWriteLock | 允许并发读,提高吞吐量 |
| 高性能读场景 | StampedLock | 支持乐观读,无锁访问 |
| 低竞争环境 | CAS操作 | 避免线程阻塞,性能高 |
| 公平性要求 | ReentrantLock(true) | 支持公平调度 |
| 可中断任务 | ReentrantLock | 支持中断等待 |
| 递归调用 | synchronized/ReentrantLock | 支持重入 |
2. synchronized 关键字
2.1 synchronized 基本用法
- 实例方法同步
- 静态方法同步
- 代码块同步
- 双重检查锁定
java
1public class Counter {2 private int count = 0;3 4 // 实例方法同步 - 锁是当前实例(this)5 public synchronized void increment() {6 count++;7 }8 9 public synchronized int getCount() {10 return count;11 }12}1314// 使用方式15Counter counter = new Counter();16// 多个线程调用同一实例的方法17counter.increment(); // 线程安全1819// 注意: 不同实例之间的方法调用不受影响20Counter counter1 = new Counter();21Counter counter2 = new Counter();22// 这两个调用可以并发执行,互不干扰23counter1.increment();24counter2.increment();java
1public class StaticCounter {2 private static int count = 0;3 4 // 静态方法同步 - 锁是类对象(StaticCounter.class)5 public static synchronized void increment() {6 count++;7 }8 9 public static synchronized int getCount() {10 return count;11 }12}1314// 使用方式15// 多个线程调用同一个类的静态方法16StaticCounter.increment(); // 线程安全1718// 注意: 所有线程都会竞争同一个锁(类锁),19// 即使是不同实例的调用也会相互影响20new Thread(() -> StaticCounter.increment()).start();21new Thread(() -> StaticCounter.increment()).start();java
1public class BlockSynchronization {2 private int count = 0;3 private final Object lock = new Object(); // 显式锁对象4 5 public void increment() {6 // 只同步关键代码块,使用自定义锁对象7 synchronized (lock) {8 count++;9 }10 11 // 这里是非同步代码12 System.out.println("Current count: " + count);13 }14 15 public void otherMethod() {16 // 使用this作为锁对象17 synchronized (this) {18 // 这里的同步与synchronized实例方法效果相同19 System.out.println("Using this as lock");20 }21 22 // 使用类对象作为锁23 synchronized (BlockSynchronization.class) {24 // 这里的同步与synchronized静态方法效果相同25 System.out.println("Using class object as lock");26 }27 }28 29 // 特定对象锁的优点30 // 1. 更细粒度的锁控制31 // 2. 避免锁定整个对象32 // 3. 可以使用多个不同的锁对象控制不同的资源33 34 private final Object resourceALock = new Object();35 private final Object resourceBLock = new Object();36 37 public void updateResourceA() {38 synchronized (resourceALock) {39 // 更新资源A40 }41 }42 43 public void updateResourceB() {44 synchronized (resourceBLock) {45 // 更新资源B46 }47 }48 // 资源A和B可以并发访问,互不影响49}java
1public class Singleton {2 // volatile确保多线程可见性和防止指令重排序3 private volatile static Singleton instance;4 5 private Singleton() {}6 7 public static Singleton getInstance() {8 // 第一次检查 - 不需要同步9 if (instance == null) {10 // 同步块 - 只有需要创建实例时才同步11 synchronized (Singleton.class) {12 // 第二次检查 - 防止多个线程都通过了第一次检查13 if (instance == null) {14 instance = new Singleton();15 }16 }17 }18 return instance;19 }20 21 /* 22 * 为什么需要volatile?23 * 防止指令重排序导致的部分初始化对象问题:24 * 1. 分配内存空间25 * 2. 初始化对象26 * 3. 将引用指向内存空间27 * 28 * 如果发生2和3的重排序,其他线程可能会看到一个未完全初始化的对象29 */30}2.2 synchronized 的特性
- 可重入性
- 自动释放
- 内存可见性
- 原子性
可重入性(Reentrancy)是指同一个线程可以多次获取同一把锁。
java
1public class ReentrantExample {2 // 可重入性示例3 public synchronized void outer() {4 System.out.println("Entering outer method");5 // 同一线程可以再次获取相同的锁6 inner();7 System.out.println("Exiting outer method");8 }9 10 public synchronized void inner() {11 System.out.println("Executing inner method");12 }13}1415// 执行结果:16// Entering outer method17// Executing inner method <-- 可以重入获取锁18// Exiting outer methodsynchronized 会在以下情况自动释放锁:
- 同步代码块执行完成
- 同步方法返回
- 同步代码块/方法中抛出未捕获的异常
java
1public synchronized void safeMethod() {2 try {3 // 可能抛出异常的代码4 if (Math.random() < 0.5) {5 throw new RuntimeException("示例异常");6 }7 System.out.println("操作成功");8 } catch (Exception e) {9 System.out.println("捕获到异常: " + e.getMessage());10 // 即使抛出异常,锁也会被正确释放11 }12 // 无需手动释放锁13}synchronized 保证了内存可见性,确保一个线程对共享变量的修改对其他线程可见。
java
1public class MemoryVisibility {2 private int counter = 0;3 private boolean flag = false;4 5 public synchronized void write() {6 counter = 42;7 flag = true;8 // 退出同步块时,修改会刷新到主内存9 }10 11 public synchronized void read() {12 // 进入同步块时,会从主内存读取最新值13 if (flag) {14 // 一定能看到counter的最新值(42)15 System.out.println("Counter: " + counter);16 }17 }18}工作原理:
- 获取锁时,清空工作内存中的共享变量,从主内存重新加载
- 释放锁时,将工作内存中的修改刷新到主内存
synchronized 确保了多个操作作为一个原子单元执行,不会被其他线程中断。
java
1public class AtomicityExample {2 private int counter = 0;3 private int total = 0;4 5 // 非原子操作变成了原子操作6 public synchronized void increment() {7 // counter++ 实际上是三个操作:8 // 1. 读取counter的值9 // 2. 加110 // 3. 将结果写回counter11 counter++;12 total += counter;13 14 // 整个方法作为一个原子单元,其他线程无法在中间观察到部分更新的状态15 }16 17 public synchronized int[] getValues() {18 // 原子地返回当前状态的快照19 return new int[] { counter, total };20 }21}2.3 synchronized 的局限性
synchronized 局限性概览
| 局限性 | 描述 | 解决方案 |
|---|---|---|
| 无法中断等待 | 线程等待锁时无法响应中断 | 使用 ReentrantLock.lockInterruptibly() |
| 无法设置超时 | 无法设置获取锁的超时时间 | 使用 ReentrantLock.tryLock(time, unit) |
| 无法实现公平性 | 无法保证FIFO顺序 | 使用 ReentrantLock(true) 创建公平锁 |
| 无法实现读写分离 | 无法让多个读线程并行访问 | 使用 ReadWriteLock 读写锁 |
| 无法知道锁状态 | 无法查询锁是否被占用 | 使用 Lock 接口的相关方法 |
- 无法中断等待
- 无法设置超时
- 无法实现公平性
java
1import java.util.concurrent.locks.Lock;2import java.util.concurrent.locks.ReentrantLock;34public class InterruptionExample {5 private final Object syncLock = new Object();6 private final Lock interruptibleLock = new ReentrantLock();7 8 public void synchronizedMethod() {9 // synchronized 不响应中断10 synchronized (syncLock) {11 try {12 System.out.println("获取synchronized锁");13 Thread.sleep(10000); // 持有锁10秒14 } catch (InterruptedException e) {15 System.out.println("即使线程被中断,synchronized也会继续执行到完成");16 Thread.currentThread().interrupt();17 }18 }19 }20 21 public void interruptibleMethod() {22 try {23 // ReentrantLock 可以响应中断24 interruptibleLock.lockInterruptibly();25 try {26 System.out.println("获取可中断锁");27 Thread.sleep(10000); // 尝试持有锁10秒28 } finally {29 interruptibleLock.unlock();30 }31 } catch (InterruptedException e) {32 System.out.println("锁等待被中断,可以执行其他操作");33 }34 }35 36 public static void main(String[] args) throws InterruptedException {37 InterruptionExample example = new InterruptionExample();38 39 // 先占用锁40 Thread blocker = new Thread(() -> {41 example.synchronizedMethod();42 });43 blocker.start();44 Thread.sleep(100); // 确保blocker获得锁45 46 // 尝试获取锁并中断47 Thread waiter = new Thread(() -> {48 try {49 System.out.println("等待锁...");50 example.synchronizedMethod(); // 这里会被阻塞51 } finally {52 System.out.println("waiter线程结束");53 }54 });55 56 waiter.start();57 Thread.sleep(1000); // 让waiter等待一段时间58 59 System.out.println("尝试中断waiter线程");60 waiter.interrupt(); // 中断waiter线程61 62 // 注意: waiter线程会一直等待,直到blocker释放锁63 // synchronized不响应中断64 }65}java
1import java.util.concurrent.TimeUnit;2import java.util.concurrent.locks.Lock;3import java.util.concurrent.locks.ReentrantLock;45public class TimeoutExample {6 private final Object syncLock = new Object();7 private final Lock timeoutLock = new ReentrantLock();8 9 public void synchronizedMethod() {10 // synchronized 无法设置超时11 synchronized (syncLock) {12 System.out.println("获取synchronized锁");13 sleep(5000); // 持有锁5秒14 }15 }16 17 public boolean timeoutMethod(long timeout) {18 try {19 // ReentrantLock 支持超时获取锁20 if (timeoutLock.tryLock(timeout, TimeUnit.MILLISECONDS)) {21 try {22 System.out.println("在超时时间内获取到锁");23 return true;24 } finally {25 timeoutLock.unlock();26 }27 } else {28 System.out.println("获取锁超时");29 return false;30 }31 } catch (InterruptedException e) {32 System.out.println("等待锁时被中断");33 return false;34 }35 }36 37 private void sleep(long millis) {38 try {39 Thread.sleep(millis);40 } catch (InterruptedException e) {41 Thread.currentThread().interrupt();42 }43 }44}java
1import java.util.concurrent.locks.Lock;2import java.util.concurrent.locks.ReentrantLock;34public class FairnessExample {5 // synchronized 无法设置公平性6 private final Object syncLock = new Object();7 8 // ReentrantLock 可以创建公平锁9 private final Lock fairLock = new ReentrantLock(true);10 private final Lock unfairLock = new ReentrantLock(false);11 12 public void testFairness() {13 // 创建多个线程竞争锁14 for (int i = 0; i < 5; i++) {15 final int threadId = i;16 new Thread(() -> {17 for (int j = 0; j < 3; j++) {18 // 使用公平锁19 fairLock.lock();20 try {21 System.out.println("线程 " + threadId + 22 " 获取公平锁,尝试次数 " + j);23 sleep(100);24 } finally {25 fairLock.unlock();26 }27 }28 }).start();29 }30 }31 32 // 公平锁与非公平锁的区别:33 // 1. 公平锁: 按照FIFO顺序获取锁,等待时间最长的线程优先获取34 // 2. 非公平锁: 允许"插队",新到达的线程可能比等待中的线程先获取锁35 // 3. synchronized 默认是非公平的,且无法配置为公平模式36 37 private void sleep(long millis) {38 try {39 Thread.sleep(millis);40 } catch (InterruptedException e) {41 Thread.currentThread().interrupt();42 }43 }44}3. Lock 接口
3.1 Lock 接口基本用法
- 基本用法
- 可中断锁
- 超时锁
- 公平锁
java
1import java.util.concurrent.locks.Lock;2import java.util.concurrent.locks.ReentrantLock;34public class LockBasicExample {5 private final Lock lock = new ReentrantLock();6 private int counter = 0;7 8 public void increment() {9 // 获取锁10 lock.lock();11 try {12 // 临界区 - 受锁保护的代码13 counter++;14 } finally {15 // 必须在finally块中释放锁16 lock.unlock();17 }18 }19 20 public int getCount() {21 lock.lock();22 try {23 return counter;24 } finally {25 lock.unlock();26 }27 }28 29 public static void main(String[] args) {30 LockBasicExample example = new LockBasicExample();31 32 // 创建多个线程并发增加计数器33 for (int i = 0; i < 10; i++) {34 new Thread(() -> {35 for (int j = 0; j < 1000; j++) {36 example.increment();37 }38 }).start();39 }40 41 // 等待一段时间后查看结果42 try {43 Thread.sleep(1000);44 } catch (InterruptedException e) {45 Thread.currentThread().interrupt();46 }47 48 System.out.println("计数器最终值: " + example.getCount());49 }50}java
1import java.util.concurrent.locks.Lock;2import java.util.concurrent.locks.ReentrantLock;34public class InterruptibleLockExample {5 private final Lock lock = new ReentrantLock();6 7 public void methodWithInterruptibleLock() {8 System.out.println(Thread.currentThread().getName() + " 尝试获取锁");9 10 try {11 // 可中断的获取锁,允许响应中断12 lock.lockInterruptibly();13 try {14 System.out.println(Thread.currentThread().getName() + " 获取到锁");15 // 模拟长时间操作16 Thread.sleep(5000);17 } finally {18 lock.unlock();19 System.out.println(Thread.currentThread().getName() + " 释放了锁");20 }21 } catch (InterruptedException e) {22 System.out.println(Thread.currentThread().getName() + " 在等待锁的过程中被中断");23 }24 }25 26 public static void main(String[] args) throws InterruptedException {27 InterruptibleLockExample example = new InterruptibleLockExample();28 29 // 线程1先获取锁30 Thread t1 = new Thread(example::methodWithInterruptibleLock, "Thread-1");31 t1.start();32 Thread.sleep(100); // 确保线程1获取到锁33 34 // 线程2尝试获取已经被占用的锁35 Thread t2 = new Thread(example::methodWithInterruptibleLock, "Thread-2");36 t2.start();37 Thread.sleep(1000); // 让线程2等待一段时间38 39 // 中断线程240 System.out.println("主线程中断 Thread-2");41 t2.interrupt();42 }43}java
1import java.util.concurrent.TimeUnit;2import java.util.concurrent.locks.Lock;3import java.util.concurrent.locks.ReentrantLock;45public class TimeoutLockExample {6 private final Lock lock = new ReentrantLock();7 8 public void methodWithTimeout(long timeout, TimeUnit unit) {9 System.out.println(Thread.currentThread().getName() + 10 " 尝试获取锁,超时时间: " + timeout + " " + unit);11 12 try {13 // 尝试在指定时间内获取锁14 boolean acquired = lock.tryLock(timeout, unit);15 if (acquired) {16 try {17 System.out.println(Thread.currentThread().getName() + " 获取到锁");18 // 模拟操作19 Thread.sleep(unit.toMillis(timeout / 2));20 } finally {21 lock.unlock();22 System.out.println(Thread.currentThread().getName() + " 释放了锁");23 }24 } else {25 System.out.println(Thread.currentThread().getName() + " 获取锁超时");26 // 执行替代逻辑27 performAlternativeAction();28 }29 } catch (InterruptedException e) {30 System.out.println(Thread.currentThread().getName() + " 被中断");31 }32 }33 34 private void performAlternativeAction() {35 System.out.println(Thread.currentThread().getName() + " 执行替代操作");36 }37 38 public static void main(String[] args) throws InterruptedException {39 TimeoutLockExample example = new TimeoutLockExample();40 41 // 线程1获取锁并长时间持有42 Thread t1 = new Thread(() -> {43 example.methodWithTimeout(10, TimeUnit.SECONDS);44 }, "Thread-1");45 46 // 线程2尝试获取锁,但设置较短的超时时间47 Thread t2 = new Thread(() -> {48 example.methodWithTimeout(2, TimeUnit.SECONDS);49 }, "Thread-2");50 51 t1.start();52 Thread.sleep(100); // 确保线程1获取到锁53 t2.start(); // 线程2尝试获取锁但会超时54 }55}java
1import java.util.concurrent.locks.Lock;2import java.util.concurrent.locks.ReentrantLock;34public class FairLockExample {5 // 创建公平锁 - 按照FIFO顺序获取6 private final Lock fairLock = new ReentrantLock(true);7 8 // 创建非公平锁(默认) - 不保证获取顺序9 private final Lock unfairLock = new ReentrantLock(false);10 11 public void useFairLock(int id) {12 fairLock.lock();13 try {14 System.out.println("线程 " + id + " 获取到公平锁");15 // 短暂持有锁16 Thread.sleep(100);17 } catch (InterruptedException e) {18 Thread.currentThread().interrupt();19 } finally {20 fairLock.unlock();21 }22 }23 24 public void useUnfairLock(int id) {25 unfairLock.lock();26 try {27 System.out.println("线程 " + id + " 获取到非公平锁");28 // 短暂持有锁29 Thread.sleep(100);30 } catch (InterruptedException e) {31 Thread.currentThread().interrupt();32 } finally {33 unfairLock.unlock();34 }35 }36 37 // 公平锁特点:38 // 1. 按照请求锁的顺序获得锁(FIFO)39 // 2. 等待时间长的线程优先获得锁40 // 3. 性能比非公平锁略低,但避免了"饥饿"问题41}3.2 ReentrantLock 高级特性
- 获取锁信息
- 条件变量
- 多个条件变量
java
1import java.util.concurrent.locks.Lock;2import java.util.concurrent.locks.ReentrantLock;34public class LockInfoExample {5 private final ReentrantLock lock = new ReentrantLock();6 7 public void methodWithInfo() {8 lock.lock();9 try {10 System.out.println("当前线程: " + Thread.currentThread().getName());11 System.out.println("持有锁的线程: " + lock.getOwner());12 System.out.println("等待队列长度: " + lock.getQueueLength());13 System.out.println("是否有线程等待: " + lock.hasQueuedThreads());14 System.out.println("当前线程是否在等待队列中: " + lock.hasQueuedThread(Thread.currentThread()));15 } finally {16 lock.unlock();17 }18 }19}java
1import java.util.concurrent.locks.Lock;2import java.util.concurrent.locks.ReentrantLock;3import java.util.concurrent.locks.Condition;45public class ConditionExample {6 private final ReentrantLock lock = new ReentrantLock();7 private final Condition condition = lock.newCondition();8 private boolean flag = false;9 10 public void await() {11 lock.lock();12 try {13 while (!flag) {14 System.out.println("等待条件满足");15 condition.await(); // 等待条件16 }17 System.out.println("条件满足,继续执行");18 } catch (InterruptedException e) {19 Thread.currentThread().interrupt();20 } finally {21 lock.unlock();22 }23 }24 25 public void signal() {26 lock.lock();27 try {28 flag = true;29 System.out.println("设置条件并通知");30 condition.signal(); // 通知等待的线程31 } finally {32 lock.unlock();33 }34 }35 36 public void signalAll() {37 lock.lock();38 try {39 flag = true;40 System.out.println("设置条件并通知所有等待线程");41 condition.signalAll(); // 通知所有等待的线程42 } finally {43 lock.unlock();44 }45 }46}java
1import java.util.concurrent.locks.Lock;2import java.util.concurrent.locks.ReentrantLock;3import java.util.concurrent.locks.Condition;4import java.util.Queue;5import java.util.LinkedList;67public class MultipleConditionExample {8 private final ReentrantLock lock = new ReentrantLock();9 private final Condition notEmpty = lock.newCondition();10 private final Condition notFull = lock.newCondition();11 private final int capacity = 10;12 private int count = 0;13 private final Queue<String> queue = new LinkedList<>();14 15 public void put(String item) {16 lock.lock();17 try {18 while (queue.size() >= capacity) {19 System.out.println("队列已满,等待消费");20 notFull.await();21 }22 queue.offer(item);23 System.out.println("生产一个元素,当前数量: " + count);24 notEmpty.signal(); // 通知消费者25 } catch (InterruptedException e) {26 Thread.currentThread().interrupt();27 } finally {28 lock.unlock();29 }30 }31 32 public void take() {33 lock.lock();34 try {35 while (queue.isEmpty()) {36 System.out.println("队列为空,等待生产");37 notEmpty.await(); // 等待而不是忙等待38 }39 String item = queue.poll();40 System.out.println("消费一个元素,当前数量: " + count);41 notFull.signal(); // 通知生产者42 } catch (InterruptedException e) {43 Thread.currentThread().interrupt();44 } finally {45 lock.unlock();46 }47 }48}4. 读写锁 (ReadWriteLock)
4.1 读写锁基本用法
读写锁原理与特性
读写锁允许并发读取但互斥写入,适合读多写少场景:
- 读锁共享:多个线程可以同时持有读锁
- 写锁独占:写锁是排他的,一次只能有一个线程持有
- 读写互斥:持有写锁时,其他线程无法获取读锁;持有读锁时,其他线程无法获取写锁
- 写锁优先级:在一些实现中,写锁优先级高于读锁,防止写入线程"饥饿"
ReentrantReadWriteLock具有以下特性:
- 可重入性
- 可选择公平/非公平锁
- 锁降级(写锁→读锁)支持
- 锁升级(读锁→写锁)不支持
- 基本用法
- 缓存实现
- 锁降级
java
1import java.util.concurrent.locks.ReadWriteLock;2import java.util.concurrent.locks.ReentrantReadWriteLock;3import java.util.concurrent.locks.Lock;45public class ReadWriteLockBasic {6 private final ReadWriteLock rwLock = new ReentrantReadWriteLock();7 private final Lock readLock = rwLock.readLock();8 private final Lock writeLock = rwLock.writeLock();9 10 private String data = "初始数据";11 12 // 读操作 - 允许并发读取13 public String read() {14 readLock.lock(); // 获取读锁15 try {16 System.out.println(Thread.currentThread().getName() + " 读取数据: " + data);17 // 模拟读取操作耗时18 Thread.sleep(100);19 return data;20 } catch (InterruptedException e) {21 Thread.currentThread().interrupt();22 return null;23 } finally {24 readLock.unlock(); // 释放读锁25 }26 }27 28 // 写操作 - 独占访问29 public void write(String newData) {30 writeLock.lock(); // 获取写锁31 try {32 System.out.println(Thread.currentThread().getName() + " 写入数据: " + newData);33 // 模拟写入操作耗时34 Thread.sleep(200);35 data = newData;36 } catch (InterruptedException e) {37 Thread.currentThread().interrupt();38 } finally {39 writeLock.unlock(); // 释放写锁40 }41 }42 43 public static void main(String[] args) {44 final ReadWriteLockBasic example = new ReadWriteLockBasic();45 46 // 创建多个读线程47 for (int i = 0; i < 5; i++) {48 new Thread(example::read, "Reader-" + i).start();49 }50 51 // 创建写线程52 new Thread(() -> example.write("新数据"), "Writer-1").start();53 54 // 再创建读线程55 for (int i = 5; i < 10; i++) {56 new Thread(example::read, "Reader-" + i).start();57 }58 }59}java
1import java.util.HashMap;2import java.util.Map;3import java.util.concurrent.locks.ReadWriteLock;4import java.util.concurrent.locks.ReentrantReadWriteLock;5import java.util.concurrent.locks.Lock;67/**8 * 使用读写锁实现线程安全的缓存9 */10public class ReadWriteLockCache<K, V> {11 private final Map<K, V> cache = new HashMap<>();12 private final ReadWriteLock rwLock = new ReentrantReadWriteLock();13 private final Lock readLock = rwLock.readLock();14 private final Lock writeLock = rwLock.writeLock();15 16 /**17 * 获取缓存值18 * 如果缓存未命中,则加载数据19 */20 public V get(K key) {21 V value = null;22 23 // 先尝试从缓存中读取,使用读锁24 readLock.lock();25 try {26 value = cache.get(key);27 if (value != null) {28 System.out.println("缓存命中: " + key + " = " + value);29 return value;30 }31 } finally {32 readLock.unlock();33 }34 35 // 缓存未命中,需要加载数据并写入缓存,使用写锁36 writeLock.lock();37 try {38 // 双重检查,防止其他线程已经加载39 value = cache.get(key);40 if (value == null) {41 // 从数据源加载数据42 value = loadFromDataSource(key);43 cache.put(key, value);44 System.out.println("缓存写入: " + key + " = " + value);45 }46 return value;47 } finally {48 writeLock.unlock();49 }50 }51 52 /**53 * 直接更新缓存54 */55 public void put(K key, V value) {56 writeLock.lock();57 try {58 cache.put(key, value);59 System.out.println("缓存更新: " + key + " = " + value);60 } finally {61 writeLock.unlock();62 }63 }64 65 /**66 * 删除缓存项67 */68 public V remove(K key) {69 writeLock.lock();70 try {71 V value = cache.remove(key);72 System.out.println("缓存移除: " + key);73 return value;74 } finally {75 writeLock.unlock();76 }77 }78 79 /**80 * 清空缓存81 */82 public void clear() {83 writeLock.lock();84 try {85 cache.clear();86 System.out.println("缓存已清空");87 } finally {88 writeLock.unlock();89 }90 }91 92 /**93 * 获取缓存大小94 */95 public int size() {96 readLock.lock();97 try {98 return cache.size();99 } finally {100 readLock.unlock();101 }102 }103 104 /**105 * 模拟从数据源加载数据106 */107 @SuppressWarnings("unchecked")108 private V loadFromDataSource(K key) {109 System.out.println("从数据源加载数据: " + key);110 // 模拟数据源访问延迟111 try {112 Thread.sleep(200);113 } catch (InterruptedException e) {114 Thread.currentThread().interrupt();115 }116 // 模拟数据,实际应用中应该是真实数据源117 return (V) ("Data_" + key);118 }119}java
1import java.util.concurrent.locks.ReadWriteLock;2import java.util.concurrent.locks.ReentrantReadWriteLock;3import java.util.concurrent.locks.Lock;45/**6 * 锁降级示例 - 从写锁降级到读锁7 */8public class LockDowngradeExample {9 private final ReadWriteLock rwLock = new ReentrantReadWriteLock();10 private final Lock readLock = rwLock.readLock();11 private final Lock writeLock = rwLock.writeLock();12 13 private int data = 0;14 private boolean updated = false;15 16 /**17 * 锁降级 - 正确示例18 * 写锁 -> 获取读锁 -> 释放写锁 -> 使用读锁19 */20 public void processDataWithDowngrade() {21 // 首先获取写锁22 writeLock.lock();23 try {24 System.out.println("获取写锁,准备更新数据");25 // 更新数据26 data = (int)(Math.random() * 100);27 updated = true;28 29 // 在释放写锁之前,先获取读锁30 // 这是锁降级的关键步骤31 readLock.lock();32 System.out.println("获取读锁,进行锁降级");33 } finally {34 // 释放写锁,但仍持有读锁35 writeLock.unlock();36 System.out.println("释放写锁,完成锁降级");37 }38 39 // 这里只持有读锁,其他线程可以获取读锁,但不能获取写锁40 try {41 // 使用已更新的数据42 if (updated) {43 System.out.println("使用已更新的数据: " + data);44 }45 } finally {46 // 最后释放读锁47 readLock.unlock();48 System.out.println("释放读锁");49 }50 }51 52 /**53 * 为什么需要锁降级?54 * 1. 保证数据可见性 - 当线程完成写操作后,可能需要读取刚写入的数据55 * 2. 避免数据不一致 - 如果直接释放写锁,其他线程可能修改数据56 * 3. 提高并发性能 - 锁降级允许其他读线程访问,提高并发性57 */58 59 /**60 * 锁升级是不允许的61 * 读锁 -> 写锁 会导致死锁62 */63 public void attemptLockUpgrade() {64 readLock.lock();65 try {66 System.out.println("获取读锁");67 68 // 尝试获取写锁 - 这会导致死锁69 boolean acquired = false;70 try {71 System.out.println("尝试获取写锁(这会导致死锁)");72 73 // tryLock()可以避免死锁,但会获取失败74 acquired = writeLock.tryLock();75 76 if (acquired) {77 try {78 System.out.println("成功获取写锁(实际上这行代码不会执行)");79 data = 999;80 } finally {81 writeLock.unlock();82 }83 } else {84 System.out.println("无法获取写锁,因为已经持有读锁");85 }86 } finally {87 if (acquired) {88 writeLock.unlock();89 }90 }91 } finally {92 readLock.unlock();93 System.out.println("释放读锁");94 }95 }96 97 public static void main(String[] args) {98 LockDowngradeExample example = new LockDowngradeExample();99 100 System.out.println("=== 锁降级示例 ===");101 example.processDataWithDowngrade();102 103 System.out.println("\n=== 尝试锁升级示例 ===");104 example.attemptLockUpgrade();105 }106}4.2 读写锁的性能特性
- 性能测试
- 适用场景
- 锁类型对比
java
1import java.util.concurrent.ExecutorService;2import java.util.concurrent.Executors;3import java.util.concurrent.TimeUnit;4import java.util.concurrent.atomic.AtomicInteger;5import java.util.concurrent.locks.ReadWriteLock;6import java.util.concurrent.locks.ReentrantReadWriteLock;7import java.util.concurrent.locks.Lock;8import java.util.concurrent.locks.ReentrantLock;910/**11 * 读写锁与独占锁性能对比测试12 */13public class ReadWriteLockPerformanceTest {14 // 测试数据15 private int sharedData = 0;16 17 // 计数器18 private final AtomicInteger readCount = new AtomicInteger(0);19 private final AtomicInteger writeCount = new AtomicInteger(0);20 21 // 读写锁22 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();23 private final Lock readLock = readWriteLock.readLock();24 private final Lock writeLock = readWriteLock.writeLock();25 26 // 独占锁27 private final Lock exclusiveLock = new ReentrantLock();28 29 /**30 * 使用读写锁读取数据31 */32 public int readWithReadWriteLock() {33 readLock.lock();34 try {35 // 模拟读取操作36 Thread.sleep(1);37 readCount.incrementAndGet();38 return sharedData;39 } catch (InterruptedException e) {40 Thread.currentThread().interrupt();41 return 0;42 } finally {43 readLock.unlock();44 }45 }46 47 /**48 * 使用读写锁写入数据49 */50 public void writeWithReadWriteLock(int value) {51 writeLock.lock();52 try {53 // 模拟写入操作54 Thread.sleep(5);55 sharedData = value;56 writeCount.incrementAndGet();57 } catch (InterruptedException e) {58 Thread.currentThread().interrupt();59 } finally {60 writeLock.unlock();61 }62 }63 64 /**65 * 使用独占锁读取数据66 */67 public int readWithExclusiveLock() {68 exclusiveLock.lock();69 try {70 // 模拟读取操作71 Thread.sleep(1);72 readCount.incrementAndGet();73 return sharedData;74 } catch (InterruptedException e) {75 Thread.currentThread().interrupt();76 return 0;77 } finally {78 exclusiveLock.unlock();79 }80 }81 82 /**83 * 使用独占锁写入数据84 */85 public void writeWithExclusiveLock(int value) {86 exclusiveLock.lock();87 try {88 // 模拟写入操作89 Thread.sleep(5);90 sharedData = value;91 writeCount.incrementAndGet();92 } catch (InterruptedException e) {93 Thread.currentThread().interrupt();94 } finally {95 exclusiveLock.unlock();96 }97 }98 99 /**100 * 性能测试101 */102 public void performanceTest(int threadCount, int duration, boolean useReadWriteLock) {103 System.out.println("开始测试 " + 104 (useReadWriteLock ? "ReadWriteLock" : "ReentrantLock") + 105 " 线程数: " + threadCount);106 107 // 重置计数器108 readCount.set(0);109 writeCount.set(0);110 111 ExecutorService executor = Executors.newFixedThreadPool(threadCount);112 113 // 开始测试114 long startTime = System.currentTimeMillis();115 116 // 提交测试任务117 for (int i = 0; i < threadCount; i++) {118 final int threadId = i;119 executor.submit(() -> {120 try {121 while (System.currentTimeMillis() - startTime < duration) {122 // 90%的概率进行读操作,10%的概率进行写操作123 if (Math.random() < 0.9) {124 // 读操作125 if (useReadWriteLock) {126 readWithReadWriteLock();127 } else {128 readWithExclusiveLock();129 }130 } else {131 // 写操作132 if (useReadWriteLock) {133 writeWithReadWriteLock(threadId);134 } else {135 writeWithExclusiveLock(threadId);136 }137 }138 }139 } catch (Exception e) {140 e.printStackTrace();141 }142 });143 }144 145 // 等待测试完成146 executor.shutdown();147 try {148 executor.awaitTermination(duration + 1000, TimeUnit.MILLISECONDS);149 } catch (InterruptedException e) {150 Thread.currentThread().interrupt();151 }152 153 // 测试结果154 long endTime = System.currentTimeMillis();155 long totalTime = endTime - startTime;156 157 System.out.println("测试完成,用时: " + totalTime + "ms");158 System.out.println("读操作次数: " + readCount.get() + ", 读操作每秒: " + 159 (readCount.get() * 1000 / totalTime));160 System.out.println("写操作次数: " + writeCount.get() + ", 写操作每秒: " + 161 (writeCount.get() * 1000 / totalTime));162 System.out.println("总操作次数: " + (readCount.get() + writeCount.get()) + 163 ", 总操作每秒: " + ((readCount.get() + writeCount.get()) * 1000 / totalTime));164 System.out.println("--------------------------------------");165 }166 167 /**168 * 主测试方法169 */170 public static void main(String[] args) {171 ReadWriteLockPerformanceTest test = new ReadWriteLockPerformanceTest();172 173 // 测试参数174 int[] threadCounts = {4, 8, 16, 32};175 int testDuration = 5000; // 测试时间5秒176 177 System.out.println("===== ReadWriteLock vs ReentrantLock 性能测试 =====");178 System.out.println("读写比例: 90% 读, 10% 写");179 System.out.println("测试时间: " + testDuration + "ms");180 System.out.println("--------------------------------------");181 182 // 进行测试183 for (int threadCount : threadCounts) {184 // 测试独占锁185 test.performanceTest(threadCount, testDuration, false);186 187 // 测试读写锁188 test.performanceTest(threadCount, testDuration, true);189 190 System.out.println();191 }192 }193}ReadWriteLock最适合以下场景:
-
读多写少的应用
- 数据缓存
- 配置管理
- 状态监控
-
读操作耗时较长
- 复杂计算
- 大数据集遍历
- 资源密集型操作
-
不适合的场景
- 写操作频繁的应用
- 读写操作次数接近的场景
- 锁持有时间非常短的情况
| 特性 | synchronized | ReentrantLock | ReadWriteLock |
|---|---|---|---|
| 并发读取 | 不支持 | 不支持 | 支持 |
| 锁获取方式 | 阻塞式 | 可中断/超时/阻塞 | 可中断/超时/阻塞 |
| 公平性 | 不支持 | 支持 | 支持 |
| 性能(读多写少) | 低 | 中 | 高 |
| 性能(写多读少) | 中 | 高 | 中 |
| 编程复杂度 | 低 | 中 | 高 |
| 锁粒度控制 | 粗粒度 | 细粒度 | 更细粒度 |
| 避免死锁能力 | 低 | 高 | 高 |
5. 锁的最佳实践
5.1 锁的选择策略
核心原则
选择合适的锁机制需要考虑以下因素:
- 简单场景:优先使用synchronized
- 复杂需求:使用Lock接口(中断、超时、公平性)
- 读多写少:使用读写锁
- 性能要求:考虑无锁数据结构
5.2 避免死锁
避免死锁示例
java
1public class DeadlockPrevention {2 3 /**4 * 1. 固定锁顺序5 */6 public static class FixedLockOrder {7 private final Object lock1 = new Object();8 private final Object lock2 = new Object();9 10 public void method1() {11 synchronized (lock1) {12 synchronized (lock2) {13 System.out.println("method1 执行");14 }15 }16 }17 18 public void method2() {19 // 使用相同的锁顺序20 synchronized (lock1) {21 synchronized (lock2) {22 System.out.println("method2 执行");23 }24 }25 }26 }27 28 /**29 * 2. 使用超时机制30 */31 public static class TimeoutPrevention {32 private final ReentrantLock lock1 = new ReentrantLock();33 private final ReentrantLock lock2 = new ReentrantLock();34 35 public void method1() {36 if (lock1.tryLock()) {37 try {38 if (lock2.tryLock(5, TimeUnit.SECONDS)) {39 try {40 System.out.println("method1 执行");41 } finally {42 lock2.unlock();43 }44 } else {45 System.out.println("method1 获取锁2超时");46 }47 } catch (InterruptedException e) {48 Thread.currentThread().interrupt();49 } finally {50 lock1.unlock();51 }52 }53 }54 55 public void method2() {56 if (lock2.tryLock()) {57 try {58 if (lock1.tryLock(5, TimeUnit.SECONDS)) {59 try {60 System.out.println("method2 执行");61 } finally {62 lock1.unlock();63 }64 } else {65 System.out.println("method2 获取锁1超时");66 }67 } catch (InterruptedException e) {68 Thread.currentThread().interrupt();69 } finally {70 lock2.unlock();71 }72 }73 }74 }75 76 /**77 * 3. 使用锁的层次结构78 */79 public static class LockHierarchy {80 private final Object lock1 = new Object();81 private final Object lock2 = new Object();82 private final Object lock3 = new Object();83 84 public void method1() {85 synchronized (lock1) {86 synchronized (lock2) {87 synchronized (lock3) {88 System.out.println("method1 执行");89 }90 }91 }92 }93 94 public void method2() {95 synchronized (lock1) {96 synchronized (lock2) {97 System.out.println("method2 执行");98 }99 }100 }101 102 public void method3() {103 synchronized (lock1) {104 System.out.println("method3 执行");105 }106 }107 }108}5.3 性能优化技巧
锁性能优化示例
java
1public class LockPerformanceOptimization {2 3 /**4 * 1. 减少锁的粒度5 */6 public static class FineGrainedLocking {7 private final Object[] locks = new Object[16];8 private final int[] counters = new int[16];9 10 public FineGrainedLocking() {11 for (int i = 0; i < locks.length; i++) {12 locks[i] = new Object();13 }14 }15 16 public void increment(int key) {17 int index = Math.abs(key % locks.length);18 synchronized (locks[index]) {19 counters[index]++;20 }21 }22 23 public int getTotal() {24 int total = 0;25 for (int i = 0; i < locks.length; i++) {26 synchronized (locks[i]) {27 total += counters[i];28 }29 }30 return total;31 }32 }33 34 /**35 * 2. 使用读写锁提高并发性36 */37 public static class ReadWriteOptimization {38 private final ReadWriteLock lock = new ReentrantReadWriteLock();39 private final Lock readLock = lock.readLock();40 private final Lock writeLock = lock.writeLock();41 private final Map<String, String> data = new HashMap<>();42 43 public String get(String key) {44 readLock.lock();45 try {46 return data.get(key);47 } finally {48 readLock.unlock();49 }50 }51 52 public void put(String key, String value) {53 writeLock.lock();54 try {55 data.put(key, value);56 } finally {57 writeLock.unlock();58 }59 }60 61 public void putAll(Map<String, String> map) {62 writeLock.lock();63 try {64 data.putAll(map);65 } finally {66 writeLock.unlock();67 }68 }69 }70 71 /**72 * 3. 使用条件变量避免忙等待73 */74 public static class ConditionOptimization {75 private final ReentrantLock lock = new ReentrantLock();76 private final Condition notEmpty = lock.newCondition();77 private final Condition notFull = lock.newCondition();78 private final Queue<String> queue = new LinkedList<>();79 private final int capacity = 10;80 81 public void put(String item) {82 lock.lock();83 try {84 while (queue.size() >= capacity) {85 notFull.await(); // 等待而不是忙等待86 }87 queue.offer(item);88 notEmpty.signal();89 } catch (InterruptedException e) {90 Thread.currentThread().interrupt();91 } finally {92 lock.unlock();93 }94 }95 96 public String take() {97 lock.lock();98 try {99 while (queue.isEmpty()) {100 notEmpty.await(); // 等待而不是忙等待101 }102 String item = queue.poll();103 notFull.signal();104 return item;105 } catch (InterruptedException e) {106 Thread.currentThread().interrupt();107 return null;108 } finally {109 lock.unlock();110 }111 }112 }113}6. 总结
Java锁机制是并发编程的核心,掌握各种锁的特性和使用场景对于构建高性能的并发应用至关重要。
6.1 关键要点
- 锁的类型:synchronized、Lock接口、读写锁
- 锁的特性:可重入性、公平性、中断性、超时性
- 避免死锁:固定锁顺序、超时机制、层次结构
- 性能优化:减少锁粒度、读写分离、条件变量
6.2 选择建议
| 场景 | 推荐锁类型 | 原因 |
|---|---|---|
| 简单同步 | synchronized | 简单易用,自动管理 |
| 复杂需求 | ReentrantLock | 功能丰富,支持中断和超时 |
| 读多写少 | ReadWriteLock | 提高并发性能 |
| 高性能要求 | 无锁数据结构 | 避免锁竞争 |
6.3 学习建议
- 理解原理:深入理解各种锁的工作原理
- 实践验证:通过编写代码验证不同锁的效果
- 性能测试:对比不同锁的性能差异
- 最佳实践:遵循锁使用的最佳实践
通过深入理解和熟练运用这些锁机制,我们能够构建出更加高效、健壮和可维护的Java并发应用程序。
评论