一、基本概念
ReentrantReadWriteLock
是Java
并发包中提供的读写锁实现,它维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排它锁有了提升,写锁和读锁的特点总结如下。
1.1 写锁的特点
- 写锁是可重入的。
- 如果存在读锁,则写锁不能获取,只有等待其它读线程都释放了读锁,写锁才能被当前线程获取。
- 当前线程获取写锁后,它仍然可以获取读锁,而 其它线程 对于读锁和写锁的获取均被阻塞。
- 当前线程获取写锁后,再获取到读锁,随后释放掉写锁,这种操作称为锁降级。
1.2 读锁的特点
- 读锁是可重入。
- 读锁是共享的,它能够被多个线程同时获取。
- 在没有其它写线程访问时,读锁总会被成功获取。
1.3 示例
/**
* @author lizejun
**/
public class ReadWriteDemo {
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
private int data;
private void set(int data) {
writeLock.lock();
System.out.println("write lock begin");
try {
System.out.println("write data=" + data);
this.data = data;
} finally {
System.out.println("write lock end");
writeLock.unlock();
}
}
private void get() {
readLock.lock();
System.out.println("read lock begin");
try {
System.out.println("read data=" + data);
} finally {
readLock.unlock();
System.out.println("read lock end");
}
}
public static void run() {
final ReadWriteDemo demo = new ReadWriteDemo();
for (int i = 0; i < 5; i++) {
Thread pThread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(Math.round(Math.random() * 5));
demo.set((int) Math.round(Math.random() * 100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
pThread.start();
}
for (int i = 0; i < 5; i++) {
Thread cThread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(1);
demo.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
cThread.start();
}
}
}
运行结果:
读写锁示例
二、读写锁的实现
2.1 读写状态的设计
虽然我们在使用ReentrantReadWriteLock
的时候,是通过从ReentrantReadWriteLock
获得两个不同的锁writeLock()
和readLock()
,但在其内部都是通过同一个AQS
的实现类Sync
来关联到同一个同步队列。
其中读写状态依赖的也是同步器的同步状态state
,读写锁将变量切割成了两个部分,高16
位表示读,低16
位表示写,通过位运算判断当前的读写状态。
2.2 公平 & 非公平模式
在Sync
中提供了两个抽象的方法是子类需要实现的,它们表示当有别的线程也在尝试获取锁时,是否应该阻塞,它决定了锁是公平还是非公平的,用于写锁和读锁的获取逻辑中。
boolean writerShouldBlock()
boolean readerShouldBlock()
公平版本FairSync
的实现为:
static final class FairSync extends Sync {
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
hasQueuedPredecessors
表示当前是否有线程在等待。
非公平版本NonfairSync
的实现为:
static final class NonfairSync extends Sync {
final boolean writerShouldBlock() {
return false;
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
apparentlyFirstQueuedIsExclusive
表示当前获取到同步状态的线程是否占用了写锁,如果是,那么返回true
。
2.3 写锁的获取 & 释放
2.3.1 获取
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 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;
}
if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
获取写锁时分为三个步骤:
- 当前有读锁或者已经被其它线程占有了写锁,那么获取失败。
- 写锁数量大于
65535
,抛出异常。 - 如果
writerShouldBlock()
返回true
(如前所述,对于公平锁来说,指的是当前是否有其它线程在排队;对于非公平锁来说,始终是false
),或者CAS
设置失败,那么获取失败。
如果获取失败,那么将会进入同步队列中去排队;如果获取成功,一个线程可以多次地获取写锁,并增加同步状态的值。
2.3.2 释放
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
在释放写锁时,如果当前没有写锁,那么会抛出异常。如果当前有线程占有了写锁,那么当同步状态的值减少到0
,写锁才能够被释放。
2.4 读锁的获取 & 释放
2.4.1 获取
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
。这里面有一个要点:如果当前写锁已经被一个线程占有,那么只有该线程可以获取读锁,其它线程都无法获取读锁。
2.4.2 释放
protected final boolean tryReleaseShared(int unused) {
//...
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
读锁的每次释放,就是通过CAS
减少读状态,每次减少的值是(1 << 16)
。
网友评论