ReentrantReadWriteLock
ReentrantReadWriteLock,顾名思义是可重入的读写锁。它维护了两个锁, 用于读取操作的读取锁和用于写入操作的写入锁。读取锁是“共享锁”,能同时被多个线程获取;写入锁是“独占锁”,写入锁只能被一个线程锁获取。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。
ReadWriteLock并没有实现lock接口。
简单使用
public class Test {
static Map<String, String> map = new HashMap<>();
static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
Test test = new Test();
for (int i = 0; i < 5; i++) {
final String data = String.valueOf(i);
new Thread(() -> {
test.put(data, data);
}).start();
}
for (int i = 0; i < 5; i++) {
final String data = String.valueOf(i);
new Thread(() -> {
System.out.println(test.get(data));
}).start();
}
}
public String get(String key) {
lock.readLock().lock();
String s = "";
try {
System.out.println("开始读");
TimeUnit.SECONDS.sleep(1);
s = map.get(key);
System.out.println("读完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
return s;
}
public void put(String key, String value) {
lock.writeLock().lock();
try {
System.out.println("开始写");
TimeUnit.SECONDS.sleep(1);
map.put(key, value);
System.out.println("写完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
}
基本结构
ReentrantReadWriteLock中读锁和写锁都是同一个sync对象。
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
//Inner class providing readlock
private final ReentrantReadWriteLock.ReadLock readerLock;
//Inner class providing writelock
private final ReentrantReadWriteLock.WriteLock writerLock;
//Performs all synchronization mechanics
final ReentrantReadWriteLock.Sync sync;
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new ReentrantReadWriteLock.FairSync() : new ReentrantReadWriteLock.NonfairSync();
readerLock = new ReentrantReadWriteLock.ReadLock(this);
writerLock = new ReentrantReadWriteLock.WriteLock(this);
}
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends ReentrantReadWriteLock.Sync {}
static final class FairSync extends ReentrantReadWriteLock.Sync {}
public static class ReadLock implements Lock, java.io.Serializable {
private final ReentrantReadWriteLock.Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
}
public static class WriteLock implements Lock, java.io.Serializable {
private final ReentrantReadWriteLock.Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
}
}
写锁
写锁是一个支持可重入的排他锁。
获取
如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读取已经被获取或者有别的线程已经获取了写锁,则当前线程进入等待状态。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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)存在读锁
//current != getExclusiveOwnerThread()说明别的线程拥有写锁
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;
}
释放
写锁的释放与ReentrantLock的释放基本一致,每次释放都减少写状态,当写状态为0时表示写锁已经被释放。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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;
}
读锁
读锁是一个支持重进入的共享锁。
获取
如果当前线程在获取读锁时,写锁已被其他线程获取,则进入等待状态;否则成功获取读锁并增加读状态。读状态是所有线程获取读锁次数的总和,每个线程各自获取读锁的次数保存在ThreadLocal中,由线程自身维护。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果写状态不为0 并且当前线程不是持有锁的线程,失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
// 不需要被阻塞
if (!readerShouldBlock() &&
//同步状态未达上限
r < MAX_COUNT &&
//尝试CAS更新
compareAndSetState(c, c + SHARED_UNIT)) {
// 读状态为0代表还没有线程获取过读锁
if (r == 0) {
//设置当前线程为第一获取读锁的线程
//并且获取的锁的数量为1
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//已经有线程获取读锁,并且第一个线程不是当前线程
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
//rh为空或者rh不是当前线程,就把rh更新为当前线程
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
//这个时候rh一定是当前线程,并且当前线程已经释放了读锁
readHolds.set(rh);
//当前线程获得的锁数量加一
rh.count++;
}
return 1;
}
//循环重试
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (; ; ) {
int c = getState();
// 如果写状态不为0 并且当前线程不是持有锁的线程,失败
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock;
// blocking here would cause deadlock.
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
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)) {
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;
}
}
}
释放
当前线程持有锁的数量减一
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
//当前线程持有锁的数量减一
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
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;
}
}
StampedLock
StampedLock是Java 8新增的一个读写锁,它是对ReentrantReadWriteLock的改进。ReentrantReadWriteLock中只有当前没有线程持有读锁或者写锁时才能获取到写锁,这可能会导致由于读线程太多使得写线程迟迟竞争不到锁而一直处于等待状态,也就是会发生写线程饥饿现象。为解决这个问题StampedLock提供了一种乐观读的机制,在读的时候如果发生了写,就会重读而不是在读的时候直接阻塞写。
使用
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
/**
* 移动位置
* 写锁:保证了其他线程调用move函数时候会被阻塞,直到当前线程释放写锁
*/
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
/**
* 计算当前坐标到原点的距离
* 乐观读锁,获得stamp,然后验证stamp是否有效
* 如果有效,读取的内容就是正确的结果
* 如果无效,就使用普通读锁,重新获取数据
*/
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
/**
* 如果当前坐标为原点则移动到指定的位置
* 先获取读锁,判断是否是原点
* 如果是原点尝试升级为写锁:
* 1.升级成功,更新坐标值,坐标位置就不是原点位置
* 2.升级失败,释放读锁,获取写锁,然后再次循环
* 坐标位置不是原点位置的时候释放锁。
*/
void moveIfAtOrigin(double newX, double newY) {
//也可以获取乐观读锁
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp);
if (ws != 0L) {
//升级成功
stamp = ws;
x = newX;
y = newY;
break;
} else {
//升级失败
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
}
结构
并没有实现Lock或者ReadWriteLock接口,也没有用到AbstractQueuedSynchronizer,但是也是基于AQS这种思想实现的。
public class StampedLock implements java.io.Serializable {
//处理器数量,用于旋转控制
private static final int NCPU = Runtime.getRuntime().availableProcessors();
//被增加到同步队列前最大的重试次数 : 1 << 6(64) 或者 0
private static final int SPINS = (NCPU > 1) ? 1 << 6 : 0;
//在头节点处被阻塞前的最大重试次数 :1 << 10(1024) 或者 0
private static final int HEAD_SPINS = (NCPU > 1) ? 1 << 10 : 0;
//重新阻塞前的最大重试次数 :1<<16(65536) 或者 0
private static final int MAX_HEAD_SPINS = (NCPU > 1) ? 1 << 16 : 0;
//The period for yielding when waiting for overflow spinlock
private static final int OVERFLOW_YIELD_RATE = 7; // must be power 2 - 1
//The number of bits to use for reader count before overflowing
private static final int LG_READERS = 7;
// Values for lock state and stamp operations
private static final long RUNIT = 1L;
private static final long WBIT = 1L << LG_READERS; //128
private static final long RBITS = WBIT - 1L; //127
private static final long RFULL = RBITS - 1L; //126
private static final long ABITS = RBITS | WBIT; //255
private static final long SBITS = ~RBITS; // note overlap with ABITS -128
// Special value from cancelled acquire methods so caller can throw IE
private static final long INTERRUPTED = 1L;
/**
* Lock sequence/state
* 默认为256 : 1 0000 0000
*/
private transient volatile long state;
//extra reader count when state read count saturated
private transient int readerOverflow;
public StampedLock() {
state = ORIGIN(256);
}
//Head of CLH queue
private transient volatile WNode whead;
//Tail (last) of CLH queue
private transient volatile WNode wtail;
//Wait nodes
static final class WNode {
volatile WNode prev;
volatile WNode next;
volatile WNode cowait; // list of linked readers
volatile Thread thread; // non-null while possibly parked
volatile int status; // 0, WAITING(-1), or CANCELLED(1)
final int mode; // RMODE(0) or WMODE(1)
WNode(int m, WNode p) {
mode = m;
prev = p;
}
}
// views
transient ReadLockView readLockView;
transient WriteLockView writeLockView;
transient ReadWriteLockView readWriteLockView;
}
乐观读
乐观读并没有获取锁与释放锁,它只是获取一个状态标志,然后在检测下这个状态标志有没有被一个写锁更改,如果更改就尝试普通读获取锁,否则直接运行下去就可以了。
public long tryOptimisticRead() {
long s;
return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}
public boolean validate(long stamp) {
//禁止loadload排序
U.loadFence();
return (stamp & SBITS) == (state & SBITS);
}
锁转换
通过写入state变量,从而改变锁的性质。
- tryConvertToWriteLock:转换成写锁
- tryConvertToReadLock:转换成读锁
- tryConvertToOptimisticRead:转换成乐观读锁