读写锁
what
读写锁在同一时刻可以允许多个线程访问,但是在写线程访问时,所有的读线程和其他线程均被阻塞.
读写锁维护了一对锁,一个读锁,一个写锁,通过分离读锁和写锁,使得并发性,比一般的排他锁要高.
how
1、读写状态的设计
依赖自定义同步器来实现同步功能,读写状态就是同步器的状态.而读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程.
在一个整型变量上维护多种状态,就要按位切割使用这个变量.读写锁将变量切分成两个部分,高16位表示读,低16位表示写.
2、写锁的获取与释放
写锁是一个支持可重入的排他锁.如果当前线程已经获取了写锁,则增加写状态.如果当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态,
代码实现ReentrantReadWriteLock的tryAcquire方法
protected final boolean tryAcquire(int acquires){
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if(c != 0){
if(w != 0 || current != getExclusiveOwnerThread())
retrun false;
if(w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c+acquires);
return true;
}
if(writerShouldBlock() || !compareAndSetState(c, c + acquires)){
return false;
}
setExclusiveOwnerThread(current);
return true;
}
3、读锁的获取与释放
读锁是一个支持重入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问时,读锁总是会被成功地获取,而所做的也只是增加读状态.如果当前线程已经获取了读锁,则增加读状态,如果当前线程获取读锁时,写锁已被其他线程获取,则进入等待状态.
ReentrantReadWriteLock的tryAcquireShared方法:
protected final int tryAcquireShared(int unused){
for(;;){
int c = getState();
int nextc = c + (1<<16);
if(nextc < c)
throw new Error("Maximum lock count exceeded");
if(exclusiveCount(c) != 0 && owner != Thread.currentThread())
return -1;
if(compareAndSetState(c,nextc))
return 1;
}
}
读锁的每次释放,均减少读状态,减少值是(1<<16).
4、锁降级
指的是写锁降级成读锁.如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级.锁降级是指把持住写锁,再获取读锁,随后释放(先前拥有的)写锁的过程
5、思考
锁降级中,读锁的获取是否必要?
答案是必要的.主要保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程A获取了写锁,并修改了数据,那么当前线程无法感知线程A的数据更新.
如果当前线程获取读锁,即遵循锁降级的步骤,则线程T会被阻塞,直到当前线程使用数据并释放读锁之后,线程A才能获取写锁,进行数据更新.
为什么ReentrantReadWriteLock不支持锁升级(把持读锁、获取写锁、释放读锁)?
目的是保证数据的可见性,如果读锁被多个线程获取,其中任一线程成功获取了写锁,并更新数据,则更新对其他获取到的读锁线程是不可见的.