并发包里的锁总结
并发包的锁
并发包有三种常见的锁,分别是ReentrantLock、ReadWriteLock、StampedLock等。
它们的主要区别和特点:
ReentrantLock(可重入锁):
- 特点:ReentrantLock 是一种独占锁(排他锁),允许同一个线程多次获取同一把锁(可重入性)。
- 适用场景:适用于需要更灵活的锁操作、对锁的粒度要求较高、需要条件等待的场景。
- 特点:它提供了独占的锁,不支持读写锁的共享特性。
ReadWriteLock(读写锁):
- 特点:ReadWriteLock 使用读锁和写锁分离,允许多个线程同时获取读锁,但只允许一个线程获取写锁。
- 适用场景:适用于读多写少的场景,能够提高读操作的并发性能,对写操作加了排他性的限制。
- 特点:支持读锁的共享特性,不适合处理写操作频繁的场景。
StampedLock(标记锁):
- 特点:StampedLock 是 Java 8 新引入的锁机制,是读写锁的扩展,支持乐观读锁(不阻塞其他读写操作)、悲观读锁和写锁。
- 适用场景:适用于读操作远远多于写操作的场景,并且乐观读操作较为频繁的情况。
- 特点:支持乐观读和悲观读、写操作,适合读多写少的场景,能够提供更好的并发性能。
ReentrantLock
ReentrantLock是Java并发包中提供的一种可重入互斥锁(Reentrant Mutual Exclusion Lock)。它是一种独占锁,也称为独占模式的锁。
使用ReentrantLock实现一个并发安全的缓存,示例代码如下:
package io.github.forezp.concurrentlab.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo3 {
private Map<String, Object> cache = new HashMap<>();
private final ReentrantLock lock = new ReentrantLock();//默认非公平锁
public void put(String key, Object value) {
try {
lock.lock();
cache.put(key, value);
} finally {
lock.unlock();
}
}
public Object get(String key) {
try {
lock.lock();
return cache.get(key);
} finally {
lock.unlock();
}
}
}
ReadWriteLock
ReadWriteLock是Java并发包中提供的一种读写锁机制,它以更细粒度的方式控制对共享资源的并发访问。
ReadWriteLock的特点包括:
读锁共享,写锁独占:多个线程可以同时获取读锁,实现读并发性能的提升。当一个线程获取写锁时,它会独占资源,阻塞其他线程的读和写操作。
公平性选择:根据创建ReadWriteLock时的参数,可以选择是否使用公平策略。
可重入性:和ReentrantLock一样,读锁和写锁都是可重入的
读写分离:ReadWriteLock将读锁和写锁分离,这样可以允许多个读线程同时获取读锁,但只允许一个写线程获取写锁。
使用ReadWriteLock实现一个并发安全的缓存,示例代码如下:
public class ReadWriteLockDemo {
private Map<String, Object> cache = new HashMap<>();
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();//默认非公平锁
Lock readLock = readWriteLock.readLock();//读锁和写锁是互斥的
Lock writeLock = readWriteLock.writeLock();
public void put(String key, Object value) {
try {
writeLock.lock();
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
public Object get(String key) {
try {
readLock.lock();
return cache.get(key);
} finally {
readLock.unlock();
}
}
}
StampedLock
StampedLock是Java 8中引入的一种新的并发锁机制,属于读写锁的一种扩展。它比ReentrantLock和ReadWriteLock更加灵活和高效,适用于读多写少的场景。
StampedLock支持三种模式的访问:
写模式(Write Lock):与传统的独占写锁类似,一个线程获取了写锁后,其他线程无法同时获取读锁或写锁。写模式是排它的,保证了数据的一致性。
读模式(Read Lock):多个线程可以同时获取读锁,在没有线程持有写锁或等待的写锁时,读模式是共享的。读锁不会阻塞读线程,只有在有线程持有写锁时,读线程会被阻塞。
乐观读模式(Optimistic Read):**乐观读是一种特殊的读模式,与读锁类似,它允许多个线程同时进入,但不会引起冲突。**乐观读并不会阻塞其他线程获取写锁,因此是一种快速的读操作。在使用乐观读结果进行后续操作前,需要使用validate方法验证数据是否被其他线程修改。
使用StampedLock可以在读多写少的情况下提供更好的性能,但使用时需要注意乐观读操作可能会发生写冲突,需要使用validate方法进行数据验证。此外,StampedLock并不支持可重入的读模式。
使用StampedLock实现一个并发安全的缓存,示例代码如下:
package io.github.forezp.concurrentlab.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;
/**
* StampedLock 的性能之所以比 ReadWriteLock 还要好,
* 其关键是 StampedLock 支持乐观读的方式。ReadWriteLock
* 支持多个线程同时读,但是当多个线程同时读的时候,所有的写操作会被阻塞;
* 而 StampedLock 提供的乐观读,是允许一个线程获取写锁的,
* 也就是说不是所有的写操作都被阻塞。
*/
public class StampedLockDemo {
private Map<String, Object> cache = new HashMap<>();
final StampedLock sl = new StampedLock();//不可重入锁
public Object get(String key) {
long stamp = sl.tryOptimisticRead();
Object result = cache.get(key);
if (!sl.validate(stamp)) {
try {
stamp = sl.tryReadLock();
result = cache.get(key);
} finally {
sl.unlock(stamp);
}
}
return result;
}
public void write(String key, Object value) {
long stamp = 0;
try {
stamp = sl.writeLock();
cache.put(key, value);
} finally {
sl.unlock(stamp);
}
}
}