Java 中提供了多种锁机制来控制多线程环境下的并发访问。这些锁可以帮助程序员确保线程安全和数据一致性。以下是 Java 中常用的锁类型及其相关类:
1、内置锁(synchronized)
Java 的内置锁是通过 synchronized
关键字实现的。每个对象都有一个内置锁,线程在进入同步代码块或同步方法时需要获取该锁。
适用场景:
简单的同步需求:当同步代码块或方法简单,且不需要复杂的锁管理时,使用 synchronized 是最直接和方便的选择。
代码清晰性:synchronized 内置于 Java 语言中,代码简单明了,易于维护和理解。
// 同步方法和同步代码块,确保同一时间只有一个线程可以访问某些资源。
public class SynchronizedExample {
public synchronized void synchronizedMethod() {
// 同步方法
}
public void synchronizedBlock() {
synchronized(this) {
// 同步代码块
}
}
}
2、显式锁(Explicit Lock)
Java 5 引入了 java.util.concurrent.locks
包,提供了更灵活和功能更强大的锁机制。
2.1、ReentrantLock:可重入锁,类似于 synchronized
,但提供了更多功能,如定时锁定、轮询锁定和中断锁定。
适用场景:
需要高级功能:当需要尝试锁定、定时锁定、中断锁定等高级功能时,ReentrantLock 比 synchronized 更灵活。
频繁锁定/解锁:在复杂的多线程程序中,需要频繁地锁定和解锁不同的资源。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final Lock lock = new ReentrantLock();
public void lockMethod() {
// 实现可重入锁,可以多次锁定和解锁。
lock.lock();
try {
// 保护的代码
} finally {
lock.unlock();
}
}
}
2.2、ReentrantReadWriteLock:读写锁,允许多个读线程同时访问,但写线程独占访问。
适用场景:
读多写少:当读操作远多于写操作时,ReentrantReadWriteLock 能够显著提高性能,因为它允许多个读线程同时访问,但写线程独占访问。
需要读写分离:在需要区分读写操作的场景中,使用读写锁可以提高并发性能。
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
public void readMethod() {
readLock.lock();
try {
// 读取操作
} finally {
readLock.unlock();
}
}
public void writeMethod() {
writeLock.lock();
try {
// 写入操作
} finally {
writeLock.unlock();
}
}
}
2.3、StampedLock:提供了三种模式的锁:写锁、读锁和乐观读锁。比 ReentrantReadWriteLock
更高效。
适用场景:
需要乐观读锁:当大多数情况下读取操作不需要与写操作冲突时,StampedLock 提供的乐观读锁可以提高并发性能。
读多写少且需要高性能:与 ReentrantReadWriteLock 类似,但在高并发下性能更好。
private final StampedLock stampedLock = new StampedLock();
public void optimisticReadOperation() {
long stamp = stampedLock.tryOptimisticRead();
// 读取操作
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock();
try {
// 重新读取操作
} finally {
stampedLock.unlockRead(stamp);
}
}
}
public void writeOperation() {
long stamp = stampedLock.writeLock();
try {
// 线程安全的写操作
} finally {
stampedLock.unlockWrite(stamp);
}
}
3、其他同步机制
3.1、Semaphore:信号量,控制对共享资源的访问量。
适用场景:
限制并发访问数量:当需要限制对某些资源的并发访问数量时(如连接池、数据库连接),Semaphore 是理想的选择。
控制资源使用量:可以用于控制同一时间能访问某些资源的线程数。
private final Semaphore semaphore = new Semaphore(3);
public void accessResource() {
// 限制同一时间只能有指定数量的线程访问某资源。
try {
semaphore.acquire();
try {
// 访问受限资源
} finally {
semaphore.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
3.2、CountDownLatch:计数器,用于等待其他线程完成操作。
适用场景:
等待一组线程完成:当主线程需要等待其他几个线程完成任务时,使用 CountDownLatch。
一次性事件:适用于一次性事件,计数器只能使用一次。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
private final CountDownLatch latch = new CountDownLatch(3);
public void task() {
// 执行任务
latch.countDown(); // 任务完成,计数器减一
}
public void awaitCompletion() {
try {
latch.await(); // 等待所有任务完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
3.3、CyclicBarrier:栅栏,用于一组线程互相等待,直到所有线程都到达一个屏障点。
适用场景:
多线程相互等待:当一组线程需要互相等待对方完成,然后再一起继续执行时,使用 CyclicBarrier。
循环使用:与 CountDownLatch 不同,CyclicBarrier 可以重复使用。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
private final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
// 所有线程到达屏障点后执行的操作
System.out.println("All threads have reached the barrier.");
});
public void task() {
try {
// 执行任务
barrier.await(); // 等待其他线程
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
}
}