ReentrantReadWriteLock
是jdk中提供的一种相比于ReentrantLock
能提供更高的读效率的锁
一、基本使用
public static void main(String[] args) throws InterruptedException {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//获取读锁
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
//获取写锁
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
//用读锁对象读锁
readLock.lock();
log.info("加读锁成功");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//用写锁对象加写锁
writeLock.lock();
log.info("加写锁成功");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
});
t1.start();
t2.start();
}
虽然加读锁和写锁用了2个锁对象,但它们都使用的ReentrantReadWriteLock中的同一个同步器对象。
二、特点
相比于ReentrantLock
,它支持读读操作的并发,即可以支持多个线程的同时读,但读写操作之间还是会被阻塞住。
进行读操作就要加读锁,调用readerLock对象的lock方法,写操作加写锁,调用writerLock对象的lock方法。
持有读锁时再次重入要加写锁(锁升级) 这种是不支持的,会一直等待,
持有写锁时再次重入要加读锁(锁降级) 这种是支持的。
三、原理分析
相比于ReentrantLock
,ReentrantReadWriteLock
中的state低8为表示写锁的加锁状态,高8为表示读锁的加锁状态。
3.1 构造方法
先看写构造方法
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
// 加读锁和写锁时分别使用这2个内部类的对象来操作
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
/**
* Creates a new {@code ReentrantReadWriteLock} with
* default (nonfair) ordering properties.
*/
public ReentrantReadWriteLock() {
this(false);
}
//也分了公平锁和非公平锁的情况
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; }
}
3.2 加锁流程
加写锁会调用写锁对象的lock方法,加读锁会调用读锁对象的lock方法
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
//写锁对象内部类
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
//可以看到这个lock方法直接调用了aqs中的acquire方法
//尝试加锁的tryAcquire会在ReentrantReadWriteLock中的同步器子类中实现,
sync.acquire(1);
}
}
//读锁对象内部类
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
//这是加读锁的方法,lock方法调用aqs中的acquireShared方法,具体逻辑需要看aqs中的实现
public void lock() {
sync.acquireShared(1);
}
}
//同步器实现类
abstract static class Sync extends AbstractQueuedSynchronizer {
//这是加写锁的实现
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
//这个方法返回state低8为的值即写锁部分的值
int w = exclusiveCount(c);
//c不等于0表示这个同步器别的线程已经加过锁了
if (c != 0) {
// w=0表示已经加的是读锁,如果owner不是当前线程就要返回加锁失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//走到这里表示w!=0且owner是当前线程,即发生了锁重入
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//修改state,返回true
setState(c + acquires);
return true;
}
//如果c=0会走到这里, writerShouldBlock是个抽象方法,在非公平锁的实现中会返回false,
//就继续执行后边的compareAndSetState,如果成功了表示加锁成功,失败了就返回false表示加锁
//失败,在公平锁的实现中writerShouldBlock会根据当前阻塞队列中是否已经有等待的线程来决定
//返回值。
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();
//如果获取到state低八位的值不是0表示当前有写锁,锁降级不被允许所以返回加锁失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//走到这里表示没有写锁
//获取读锁的状态值
int r = sharedCount(c);
// readerShouldBlock方法是用来处理公平锁的,在公平/非公平两个内部类中有不同的实现,
//公平实现中会看当前队列中有没有在等待的读锁,
//如果有当前线程就不去抢锁了.
//非公平实现中直接返回true,当前线程会去抢锁。
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 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);
}
}
}
aqs部分源码
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
public final void acquire(int arg) {
//其中tryAcquire由子类实现,acquireQueued把线程加入阻塞队列等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//加读锁的方法
public final void acquireShared(int arg) {
// tryAcquireShared方法由子类来实现,返回小于0的数表示加锁失败,才需要执行后续逻辑
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//加读锁的方法
private void doAcquireShared(int arg) {
//创建读锁等待节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前节点的前一个节点
final Node p = node.predecessor();
if (p == head) {
//这块是读线程被唤醒后执行的操作,继续抢锁
int r = tryAcquireShared(arg);
//这个r大于0表示当前有多个线程在等待读锁,
//进if后 setHeadAndPropagate 判断下下一个节点是不是也在等待读锁,如果是把它也唤醒
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//这个方法让当前线程阻塞
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
}
至于解锁流程比较简单就不具体分析了。