锁是一种重要的同步机制,用于控制多个线程对共享资源的访问,以避免并发问题,如数据不一致、竞态条件等。Java提供了多种锁机制,每种锁都有其特点和适用场景。接下来,我将按照从简单到复杂的顺序,为大家介绍Java中常见的锁。
1. synchronized锁
synchronized
是Java中最基本的同步机制,它基于Java对象的监视器锁(Monitor Lock),也称为互斥锁(Mutex Lock)。
1.1 使用方式
synchronized
可以用于方法或代码块:
-
同步方法:直接在方法上加
synchronized
关键字,表示整个方法是同步的。public synchronized void synchronizedMethod() { // 需要同步的代码 }
-
同步代码块:通过
syncronized(对象)
的方式,对特定代码块加锁。synchronized (this) { // 对当前对象加锁 // 需要同步的代码 } synchronized (MyClass.class) { // 对类加锁 // 静态方法或类变量的同步 }
1.2 特点
-
可重入:一个线程可以多次获取同一个对象的锁。
-
不可中断:在等待锁的过程中,线程无法被中断。
-
非公平:线程获取锁的顺序是随机的,而不是按照等待时间的先后顺序。
1.3 适用场景
-
当需要简单地保护共享资源时,
synchronized
是一个非常方便的选择。 -
适用于锁的持有时间较短的场景,因为它的性能开销相对较小。
2. ReentrantLock
ReentrantLock
是java.util.concurrent.locks
包中的一个锁,提供了比synchronized
更灵活的功能。
2.1 使用方式
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock(); // 获取锁
try {
// 需要同步的代码
} finally {
lock.unlock(); // 释放锁
}
}
}
2.2 特点
-
可重入:支持线程重入。
-
可中断:支持中断等待锁的线程。
-
公平锁与非公平锁:可以通过构造函数指定是否为公平锁。
-
锁条件:可以与
Condition
对象配合,实现更复杂的线程调度。
2.3 适用场景
-
当需要更复杂的锁操作(如中断等待、公平锁)时,
ReentrantLock
是更好的选择。 -
适用于锁的持有时间较长,或者需要更精细控制锁的场景。
3. 读写锁(ReadWriteLock)
ReadWriteLock
是一种锁机制,允许多个读操作并发执行,但写操作是独占的。它适合读多写少的场景。
3.1 使用方式
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
lock.readLock().lock();
try {
// 读操作
} finally {
lock.readLock().unlock();
}
}
public void write() {
lock.writeLock().lock();
try {
// 写操作
} finally {
lock.writeLock().unlock();
}
}
}
3.2 特点
-
读读共享:多个读操作可以并发执行。
-
写写互斥:写操作是独占的。
-
读写互斥:读操作和写操作不能同时进行。
3.3 适用场景
-
当数据的读操作远多于写操作时,使用
ReadWriteLock
可以显著提高性能。
4. StampedLock
StampedLock
是Java 8引入的一种更高级的锁,结合了读写锁和乐观锁的特性。
4.1 使用方式
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private final StampedLock lock = new StampedLock();
public void read() {
long stamp = lock.tryOptimisticRead(); // 尝试乐观读
// 执行读操作
if (!lock.validate(stamp)) { // 检查是否需要重试
stamp = lock.readLock();
try {
// 重新执行读操作
} finally {
lock.unlockRead(stamp);
}
}
}
public void write() {
long stamp = lock.writeLock();
try {
// 写操作
} finally {
lock.unlockWrite(stamp);
}
}
}
4.2 特点
-
乐观锁:在读操作时,先尝试不加锁,如果检测到数据未被修改,则直接返回。
-
读写锁:支持读写锁的特性。
-
性能优化:在高并发读操作的场景下,性能优于
ReadWriteLock
。
4.3 适用场景
-
当需要高性能的读操作时,
StampedLock
是一个很好的选择。 -
适用于读多写少的场景,尤其是读操作的性能要求较高。
5. 锁的优化与选择
在实际开发中,选择合适的锁机制非常重要。以下是一些选择锁的建议:
-
简单场景:如果只需要简单的同步,优先使用
synchronized
。 -
复杂场景:需要更灵活的锁操作(如中断、公平锁)时,使用
ReentrantLock
。 -
读多写少:使用
ReadWriteLock
或StampedLock
,根据性能需求选择。 -
性能优先:在高并发读操作的场景下,优先考虑
StampedLock
。