ReentrantLock
ReentrantLock实现了Lock接口,而ReentrantLock其实相当于一副空壳,它的主要功能就是控制构造出公平锁还是非公平锁,对锁的相关操作细节都是由内部类Sync来完成。Sync继承自AQS(AbstractQueuedSynchronizer),并再衍生出两个内部类非公平锁NofairSync和公平锁FairSync。
通过ReentrantLock的的默认构造函数为非公平锁,也可通过含参构造函数来控制其构造公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
两种锁的主要区别就在于lock操作上,FairSync的lock就是直接通过AQS的acquire函数来尝试获取同步状态,但是NofairSync的lock会先尝试性的直接去获取同步状态,如果失败再通过acquire函数进行获取。
//公平锁FairSync的锁操作
final void lock() {
acquire(1);
}
//非公平锁NonfairSync的锁操作
final void lock() {
//直接尝试获取同步状态
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
AQS的acquire函数中又会调用子类的tryAcquire函数来尝试获取同步状态,而两种锁在tryAcquire函数上又有不同。
//公平锁FairSync的tryAcquire操作
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//区别在这里,公平锁会先判断同步队列有没有其他节点在等待
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//在这里可以看出可重入的思想,如果当前线程就是持有锁的线程,则直接将状态state + 1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//非公平锁nonfairTryAcquire的锁操作
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//非公平锁不管同步队列有没有节点,而是直接尝试获取同步状态
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//在这里可以看出可重入的思想,如果当前线程就是持有锁的线程,则直接将状态state + 1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
ReentrantReadWriteLock
ReentrantReadWriteLock实现自ReadWriteLock(读写锁)的接口。ReentrantReadWriteLock主要使用在数据的读取操作远多于写操作的情况下,可以提高并发性。它具有如下特征:
a. 维护了一对锁ReadLock(读锁)和WriteLock(写锁)读锁在同一时刻允许多个线程持有,而写锁在被持有时,其他尝试获取读锁或者写锁的线程都会被阻塞。读锁的lock方法会直接调用sync的acquireShared方法,从而调用tryAcquireShared方法:
//读锁获取同步状态
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//如果当前写锁数量不为0且获取到同步状态的线程不是当前线程,则直接返回-1。根据AQS的流程,此线程会被加入到同步队列中。
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//获取读锁数量
int r = sharedCount(c);
//按照当前锁的模式(公平锁/非公平锁)判断线程是否需要阻塞。
//如果不需要阻塞,且读数量小于最大数量,尝试CAS获取同步状态
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//如果读锁数量为0,则当前线程为第一个读线程
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
}
//如果数量不为0,但是当前线程就是第一个读线程(说明是重入了),设置第一个读线程的重入次数。
else if (firstReader == current) {
firstReaderHoldCount++;
}
//其他读线程(非第一个)的重入次数设置。通过在每个线程中的内存空间保存HodlerCount类(用于记录当前线程获取锁的次数),来获取相应的次数
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);
}
写锁的lock方法则会调用sync的tryAcquire方法:
//写锁获取同步状态
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//获取状态:读 + 写
int c = getState();
//通过状态获取写数量
int w = exclusiveCount(c);
//同步状态不为0,说明有线程持有同步状态
if (c != 0) {
//如果写数量为0(说明有线程在读)或者 写数量不为0(说明没有读线程,只有写线程)但是写线程不是当前线程,则返回失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//来到这里说明是写线程重入,则判断写数量是否超出最大值,超出抛异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//当前线程成功获取写锁,返回成功
setState(c + acquires);
return true;
}
/*
*同步状态为0,说明没有线程在读写。则按照当前锁的模式(公平锁/非公平锁)判断线程是否需要阻塞。
*如果需要阻塞,或者不需要阻塞但是没有成功获取同步状态,返回失败
*/
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//说明成功获取同步状态,设置当前线程为持有锁线程,并返回成功
setExclusiveOwnerThread(current);
return true;
}
b. 还维护了一个Sync,所有的锁操作都是由Sync完成的,也就是说读锁和写锁共同使用一个同步队列和一个同步状态state(在Sync里面)。在ReentrantReadWriteLock的同步队列中,将同步状态state分成了高16位(读状态)和低16位(写状态),通过位运算来获取值。
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//获取读数量,通过将state右移1位得到
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//获取写数量,通过将state和0xFFFF进行与操作来得到
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
写操作虽然会阻塞其他的写操作,但由于ReentrantReadWriteLock是可重入的锁,所以写数量会在该锁被重入的时候用于记录重入次数。
c. Sync内部含有子类NofairSync和FairSync,也就是说ReentrantReadWriteLock也是支持公平锁和非公平锁的。
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
ThreadLocal
在ReentrantReadWriteLock的readLock中,通过一个HoldCounter类来记录当前线程的重入次数,然后将其存储到readHolds中。而readHolds是在ReentrantReadWriteLock定义的一个内部静态类ThreadLocalHoldCounter,继承自ThreadLocal<HoldCounter>。
ThreadLocal是一种线程的本地存储,它为变量在每个线程中都创建了一个实例,存储在共用的ThreadLocalMap中(以线程为键,实例为值)。ThreadLocal是线程共享的,但里面存储的变量却不是共享的,因此ThreadLocal并不是用来解决多线程变量共享的问题,它只是用来方便用户存储和读取的一种存储结构。
锁降级
锁降级是指把持住当前拥有的写锁(进行数据写入)的同时,再获取到读锁进行数据读取使用,然后再释放写锁,最后释放读锁。这样做的目的是保证数据的可见性,防止本线程在数据完成写操作后,再进行读取前有其他线程修改了数据,从而让本线程先获取到读锁以保证其他线程无法加写锁,然后本线程再释放写锁,读取数据。
//这是Oracle官方的示例,体会一下。
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}