StampedLock
文章目录
1. Lock接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UFs3MUn2-1577708089191)(https://note.youdao.com/yws/res/41080/4B25E52EC8AB413AA024C2664860A2C8)]
在 Lock 接口出现之前,Java 程序是靠 synchronized 关键字实现锁功能的,而 Java SE 5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,它提供了与 synchronized 关键字类似的同步功能,只是在使用时需要显式地获取和释放锁
缺点:缺少了隐式获取释放锁的便捷性
优点:拥有锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步特性
1.1. Lock的简单使用
Lock lock = new ReentrantLock();
lock.lock();
try {
//业务逻辑
} finally {
lock.unlock();
}
1.2. Lock接口说明
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
方法名 | 含义 |
---|---|
lock() | 尝试获取锁,如果获取到锁返回否则阻塞等待 |
lockInterruptibly() | 可中断地获取锁,在锁的获取中可以中断当前线程 |
tryLock() | 非阻塞的获取锁,调用后立刻返回,如果获取成功返回true,失败返回false |
tryLock(long time, TimeUnit unit) | 超时获取锁,成功返回ture,否则超时后返回false |
unlock() | 释放锁,解锁 |
newCondition() | 获取等待通知组建,该组件和当前的锁绑定。只有当前线程获取到了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。 |
1.3. ReentrantLock的加锁实现
- 公平锁能保证:老的线程排队使用锁,新线程仍然排队使用锁。
- 非公平锁保证:老的线程排队使用锁;新的线程先去抢占,抢占失败再去排队
公平锁 非公平锁
final void lock() { final void lock() {
acquire(1); if (compareAndSetState(0, 1))
} setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) { final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); final Thread current = Thread.currentThread();
int c = getState(); int c = getState();
if (c == 0) { if (c == 0) {
if (!hasQueuedPredecessors() && if (compareAndSetState(0, acquires)) {
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); setExclusiveOwnerThread(current);
return true; return true;
} }
} }
else if (current == getExclusiveOwnerThread()) { else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; int nextc = c + acquires;
if (nextc < 0) if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded"); throw new Error("Maximum lock count exceeded");
setState(nextc); setState(nextc);
return true; return true;
} }
return false; return false;
} }
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); 头不等于尾 且 (头没有后继节点或头的后继节点所属线程不是当前线程)
}
从上面我们可以看到 公平不公平的区别有两个
- 非公平锁上来直接加锁,如果失败了才会调用
tryAcquire
- 公平锁在
tryAcquire
时只有没有前驱节点的时候才尝试取锁,而非公平锁不管三七二十一直接取锁,根本不管队列,取锁失败后会进入队列
2.ReadWriteLock接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-15G1WzKi-1577708089193)(https://note.youdao.com/yws/res/42056/FB1D0D208DE24984BD41DF885F516DFA)]
2.1. ReadWriteLock接口简单使用
public class Cache {
static Map<String, Object> map = new HashMap<String, Object>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
// 获取一个 key 对应的 value
public static final Object get(String key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
// 设置 key 对应的 value,并返回旧的 value
public static final Object put(String key, Object value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
}
}
// 清空所有的内容
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
}
Cache 组合一个非线程安全的 HashMap 作为缓存的实现,同时使用读写锁的读锁和写锁来保证 Cache 是线程安全的。
在读操作 get(String key) 方法中,需要获取读锁,这使得并发访问该方法时不会被阻塞。
写操作 put(String key,Object value) 方法和 clear()方法,在更新HashMap时必须提前获取写锁,当获取写锁后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放之后,其他读写操作才能继续
Cache 使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,同时简化了编程方式
2.2. ReadWriteLock 接口说明
public interface ReadWriteLock {
Lock readLock(); // 获取读锁对象
Lock writeLock();// 获取写锁对象
}
2.3. ReentrantReadWriteLock 实现
写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升
- 读锁 写锁 用的是同一个Sync也就是同一个AQS
- AQS的初始化由ReentrantReadWriteLock在构造函数中进行
- 默认构造非公平锁
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
...
public ReentrantReadWriteLock.WriteLock writeLock() {
return writerLock;
}
public ReentrantReadWriteLock.ReadLock readLock() {
return readerLock;
}
abstract static class Sync extends AbstractQueuedSynchronizer {
...
}
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
...
}
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
...
}
static final class NonfairSync extends Sync {
final boolean writerShouldBlock() {
return false;
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
static final class FairSync extends Sync {
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
}
2.3.1.lock 加锁
- 读锁
sync.acquireShared(1);
- 写锁
sync.acquire(1);
static int sharedCount(int c) {
return c >>> SHARED_SHIFT;
}
static int exclusiveCount(int c) {
return c & EXCLUSIVE_MASK;
}
读写锁将变量切分成了两个部分,高16位表示读,低16位表示写
sharedCount
通过位移得到高位的共享锁数量exclusiveCount
通过& 得到低位的独占锁数量
写锁-独占锁
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState(); // 得到状态
int w = exclusiveCount(c); // 得到独占写锁数量
if (c != 0) {
// (Note: 如果总数 c 不是 0 同时 写数量 w 是0 所以r读锁数量不为0 代表有线程在读数据)
if (w == 0 || current != getExclusiveOwnerThread()) {// 如果有读取中的线程,写锁获取失败 如果当前线程与持有锁的线程不是一个失败
return false;
}
if (w + exclusiveCount(acquires) > MAX_COUNT) {// 如果写锁数量超过限制
throw new Error("Maximum lock count exceeded");
}
// Reentrant acquire
setState(c + acquires); // 如果当前线程已经获取了写锁,则增加写状态
return true;
}
// 如果读写锁都没有 则尝试通过CAS的方式设置锁
if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) {
return false;
}
setExclusiveOwnerThread(current);
return true;
}
读锁-共享锁
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果有写锁 且 线程不是当前线程 加锁失败 也就是说持有读锁的线程可以继续获取写锁
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) {
return -1;
}
int r = sharedCount(c); // 得到读锁数量
if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // CAS方式修改State成功,也就是取锁成功
if (r == 0) { // 如果读锁数量是0 那么 当前线程是第一个读取线程
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) { // 如果当前线程是第一个读取线程
firstReaderHoldCount++;
} else { // 如果当前线程不是第一个读取线程
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
cachedHoldCounter = rh = readHolds.get();
} else if (rh.count == 0) {
readHolds.set(rh);
}
rh.count++; // 读取锁获取次数
}
return 1;
}
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
/*
该代码与tryAcquireShared中的代码部分冗余,
但由于不使tryAcquireShared与重试和延迟读取保持计数之间的交互复杂化,
因此总体上更简单。
*/
HoldCounter rh = null;
for (;;) {
int c = getState(); // 获取当前state
if (exclusiveCount(c) != 0) { // 当前锁被写线程持有
if (getExclusiveOwnerThread() != current) { // 如果当前线程不是持有锁的线程,无法获取读锁
return -1;
}
} else if (readerShouldBlock()) { // 如果当前线程没有被写线程持有 但是读取需要阻塞 获取锁失败
if (firstReader == current) { // 如果当前线程是第一个读取者
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0) {
readHolds.remove();
}
}
}
if (rh.count == 0) {
return -1;
}
}
}
if (sharedCount(c) == MAX_COUNT) {// 如果读取线程书来给你超过最大值
throw new Error("Maximum lock count exceeded");
}
if (compareAndSetState(c, c + SHARED_UNIT)) { // 通过CAS增加读取锁状态 返回状态
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null) {
rh = cachedHoldCounter;
}
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
} else if (rh.count == 0) {
readHolds.set(rh);
}
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
2.3.2. unlock解锁
写锁-独占锁
public void unlock() {
sync.release(1); // 释放独占锁
}
读锁-共享锁
public void unlock() {
sync.releaseShared(1); // 释放共享锁
}
2.3.3. 锁降级
锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。
锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
锁降级要解决的问题
为什么要降级而不是分段完成呢,因为如果释放了写锁在获取读锁前可能有其他线程获取到写锁,并修改了数据,那么当前线程是无法感知的。