在 Java 并发编程中,锁(Lock)是保证线程安全的关键工具。本文将全面介绍 Java 的锁机制,包括 synchronized
关键字、Lock
接口及其实现、读写锁、乐观锁与悲观锁等,帮助新手理解 Java 并发控制。
1. Java 中的锁概述
锁(Lock)用于控制多个线程对共享资源的访问。不同的锁机制可以提供不同的性能、可重入性、公平性和可中断性等特性。
2. synchronized
关键字
synchronized
是 Java 内置的同步机制,依赖于 Java 虚拟机(JVM)实现。
2.1 用法示例
public class SynchronizedExample {private int count = 0;public synchronized void increment() {count++;}
}
在上述示例中,increment
方法是同步方法,多个线程调用时会自动加锁,保证 count
变量的线程安全。
2.2 synchronized
作用范围
- 同步实例方法:锁住当前实例(
this
)。 - 同步静态方法:锁住类对象(
Class
)。 - 同步代码块:可以锁定特定对象,提高并发性。
public void method() {synchronized (this) {// 代码块}
}
2.3 synchronized
的特性
- 可重入性:一个线程获取锁后可以多次进入同步代码。
- 不可中断:线程获取锁后,其他线程只能等待。
- JVM 层面实现:使用
monitorenter
和monitorexit
指令。
3. Lock
接口(显式锁)
Lock
接口提供比 synchronized
更灵活的锁控制,主要实现类是 ReentrantLock
。
3.1 ReentrantLock
用法
import java.util.concurrent.locks.ReentrantLock;public class LockExample {private final ReentrantLock lock = new ReentrantLock();private int count = 0;public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}
}
3.2 ReentrantLock
特性
- 可重入性:和
synchronized
类似,一个线程可以多次获得相同的锁。 - 可中断:支持
lockInterruptibly()
方法,中断等待锁的线程。 - 公平锁和非公平锁:默认非公平锁,可选择公平锁保证线程按请求顺序获取锁。
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
4. ReadWriteLock
(读写锁)
ReadWriteLock
提供读锁(多个线程可读)和写锁(独占)。
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockExample {private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private int data = 0;public int read() {lock.readLock().lock();try {return data;} finally {lock.readLock().unlock();}}public void write(int value) {lock.writeLock().lock();try {data = value;} finally {lock.writeLock().unlock();}}
}
5. 乐观锁与悲观锁
5.1 悲观锁
认为竞争严重,每次访问资源都加锁(synchronized
、Lock
)。
5.2 乐观锁
认为竞争较少,使用 CAS(Compare And Swap) 机制,比如 AtomicInteger
。
import java.util.concurrent.atomic.AtomicInteger;public class AtomicExample {private final AtomicInteger count = new AtomicInteger(0);public void increment() {count.incrementAndGet();}
}
6. StampedLock
(改进的读写锁)
StampedLock
提供乐观读锁,提高并发性能。
import java.util.concurrent.locks.StampedLock;public class StampedLockExample {private final StampedLock lock = new StampedLock();private int data = 0;public int read() {long stamp = lock.tryOptimisticRead();int currentData = data;if (!lock.validate(stamp)) { // 检测数据是否被修改lock.readLock();try {currentData = data;} finally {lock.unlockRead(stamp);}}return currentData;}
}
7. ThreadLocal
变量
ThreadLocal
不是锁,而是让每个线程拥有自己的变量副本,避免锁竞争。
public class ThreadLocalExample {private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);public void increment() {threadLocal.set(threadLocal.get() + 1);}
}
8. 选择合适的锁
锁类型 | 特性 | 适用场景 |
---|---|---|
synchronized | JVM 层面,简单易用 | 适合简单同步需求 |
ReentrantLock | 可中断、支持公平锁 | 适合需要高级控制的场景 |
ReadWriteLock | 读写分离,提高并发 | 读多写少的情况 |
StampedLock | 乐观读,提高性能 | 适合高并发读的场景 |
ThreadLocal | 线程私有,无锁 | 线程隔离数据 |
9. 总结
Java 提供了多种锁机制,每种锁都有其适用场景。synchronized
适用于简单同步,Lock
提供更多控制,ReadWriteLock
适用于读多写少的情况,StampedLock
提供乐观读锁以提高并发性能。此外,ThreadLocal
可用于无锁并发,避免数据竞争。