在Java中,并发锁是用来控制多个线程对共享资源的访问,确保数据的一致性和完整性。Java提供了多种并发锁机制,包括内置锁(synchronized)、显示锁(如ReentrantLock)、原子变量、并发容器以及一些高级技巧如乐观锁和String.intern()等。
1. synchronized关键字
特点:
synchronized是Java内建的锁机制,它提供了隐式锁,也称为内部锁或监视器锁。
当一个线程获得对象的锁后,其他试图获取该锁的线程将会被阻塞,直到锁被释放。
实现原理:
每个对象都有一个内置锁和一个计数器。当线程请求锁时,JVM将计数器加一。
如果线程已经持有锁,计数器会再次增加,这允许同一个线程多次同步。
当线程完成同步代码块时,计数器减一。当计数器为零时,锁被释放。
使用场景:
适用于方法或代码块的简单同步。
优点:
简单易用,不需要手动释放锁。
缺点:
不能被中断;不支持公平性;无法设置超时。
代码示例:
public class SynchronizedExample {
private Object lock = new Object();
public void method() {
synchronized (lock) {
// 临界区代码
}
}
}
2. ReentrantLock
特点:
ReentrantLock是一个可重入互斥锁,由java.util.concurrent.locks包提供。
支持公平锁和非公平锁。
实现原理:
基于AbstractQueuedSynchronizer(AQS)框架实现。
维护了一个状态变量来跟踪锁的状态。
使用场景:
适用于需要高度自定义和灵活的同步控制的场景。
优点:
高度灵活,支持中断、超时、公平性。
缺点:
必须手动释放锁,否则可能导致死锁。
代码示例:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
}
}
3. ReadWriteLock
特点:
允许多个读线程同时访问,但只允许一个写线程。
读写锁通常用于读多写少的场合。
实现原理:
通过两个锁来实现:一个读锁和一个写锁。
读锁是共享的,写锁是独占的。
使用场景:
适用于读多写少的数据结构,如缓存系统。
优点:
提高并发性能,减少锁竞争。
缺点:
写操作可能会饥饿,如果读操作持续不断。
代码示例:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void read() {
rwLock.readLock().lock();
try {
// 读操作代码
} finally {
rwLock.readLock().unlock();
}
}
public void write() {
rwLock.writeLock().lock();
try {
// 写操作代码
} finally {
rwLock.writeLock().unlock();
}
}
}
4. StampedLock
特点:
StampedLock支持乐观读、悲观读、写锁和锁的升级。
它提供了一个版本号来避免不必要的唤醒。
实现原理:
使用了一种称为“乐观读”的技术,通过标记来避免长时间的等待。
使用CAS操作来尝试获取和释放锁。
使用场景:
适用于高并发且读多写少的场景。
优点:
高性能,特别是在高并发环境下。
缺点:
相对复杂,需要更小心地管理状态。
代码示例:
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private final StampedLock stampedLock = new StampedLock();
public void method() {
long stamp = stampedLock.writeLock();
try {
// 临界区代码
} finally {
stampedLock.unlockWrite(stamp);
}
}
}
5. Semaphore、CountDownLatch和CyclicBarrier
这三个工具类虽然不是锁,但它们常用于多线程的同步控制。
Semaphore: 控制同时访问特定资源的线程数量。适用于限制并发线程数。
CountDownLatch: 允许一个或多个线程等待其他线程完成操作。适用于等待一组线程完成任务后再执行的场景。
CyclicBarrier: 允许一组线程相互等待,直到所有线程都准备好再同时执行。适用于多线程计算数据的场景。
总结
Java提供了丰富的并发锁和同步工具,以满足不同的并发需求。从简单的synchronized到复杂的StampedLock,每种锁都有其适用场景和特定的优缺点。了解这些并发工具的原理和使用方式对于编写高效且线程安全的Java程序至关重要。在实际开发中,选择合适的锁取决于具体的需求、性能考虑以及代码的复杂性。