当读操作远远高于写操作时,这时候使用读写锁让读-读可以并发,提高性能。
这是什么意思呢?我们知道synchronized的作用标记一个方法的时候,在并发情况下,只允许一个线程去访问,其他线程在外面等待。可这很影响性能。即使这个方法是提供读取,但也只能一个线程去访问。
@Slf4j
public class TestReadWriteLock {
public static void main(String[] args) throws InterruptedException {
DataContainer dataContainer = new DataContainer();
new Thread(() -> {
dataContainer.read();
}, "t1").start();
new Thread(() -> {
dataContainer.write();
}, "t2").start();
new Thread(() -> {
dataContainer.write();
}, "t3").start();
new Thread(() -> {
dataContainer.read();
}, "t4").start();
}
}
@Slf4j
class DataContainer {
private Object data;
private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock r = rw.readLock();
private ReentrantReadWriteLock.WriteLock w = rw.writeLock();
public Object read() {
log.debug("准备获取读锁...");
r.lock();
try {
log.debug("读取中");
sleep(1);
return data;
} finally {
log.debug("释放读锁...");
r.unlock();
}
}
public void write() {
log.debug("准备获取写锁...");
w.lock();
log.debug("w入了");
try {
sleep(1);
} finally {
log.debug("释放w锁...");
w.unlock();
}
}
}
结果如下图=
17:00:11.867 c.DataContainer [t2] - 准备获取写锁...
17:00:11.867 c.DataContainer [t1] - 准备获取读锁...
17:00:11.867 c.DataContainer [t3] - 准备获取写锁...
17:00:11.867 c.DataContainer [t4] - 准备获取读锁...
17:00:11.870 c.DataContainer [t3] - w入了
17:00:12.871 c.DataContainer [t3] - 释放w锁...
17:00:12.871 c.DataContainer [t2] - w入了
17:00:13.871 c.DataContainer [t2] - 释放w锁...
17:00:13.871 c.DataContainer [t4] - 读取中
17:00:13.871 c.DataContainer [t1] - 读取中
17:00:14.872 c.DataContainer [t4] - 释放读锁...
17:00:14.872 c.DataContainer [t1] - 释放读锁...
会发现:
读操作的线程,打印信息都是同一时刻,不需要等待。
写操作的线程,有先后顺序,也就是说,需要等待,锁在着效果。
先看读操作源码:获取锁和释放锁
//第一步:
public void lock() {
this.sync.acquireShared(1);
}
//没锁前默认锁状态为0,且读锁和写锁标记不一样。读锁是共享,写锁是独占。
//第二步:tryAcquireShared方法判断有没有给其他线程占据锁
public final void acquireShared(int var1) {
if (this.tryAcquireShared(var1) < 0) {
this.doAcquireShared(var1);
}
}
//第三步
protected final int tryAcquireShared(int var1) {
//获取当前线程信息
Thread var2 = Thread.currentThread();
//获取当前锁状态
int var3 = this.getState();
//如果当前状态不等于0,给写锁占据,且占据锁的不是当前线程,返回-1表示失败。
//
if (exclusiveCount(var3) != 0 && this.getExclusiveOwnerThread() != var2) {
return -1;
} else {
//如果是占据锁的是当前线程或者锁的状态为零,且锁重入的次数小于65535,则cas改变锁重入的次数。返回1,表示成功。后面的线程由于读锁发生共享,共享次数发生改变,每次自增1.
int var4 = sharedCount(var3);
if (!this.readerShouldBlock() && var4 < 65535 && this.compareAndSetState(var3, var3 + 65536)) {
if (var4 == 0) {
this.firstReader = var2;
this.firstReaderHoldCount = 1;
} else if (this.firstReader == var2) {
++this.firstReaderHoldCount;
} else {
ReentrantReadWriteLock.Sync.HoldCounter var5 = this.cachedHoldCounter;
if (var5 != null && var5.tid == ReentrantReadWriteLock.getThreadId(var2)) {
if (var5.count == 0) {
this.readHolds.set(var5);
}
} else {
this.cachedHoldCounter = var5 = (ReentrantReadWriteLock.Sync.HoldCounter)this.readHolds.get();
}
++var5.count;
}
return 1;
} else {
return this.fullTryAcquireShared(var2);
}
}
}
读锁释放锁:
//第一步:因为锁成功会加1,锁重入次数也会加1.所以释放的时候,要每次减1.
public final boolean releaseShared(int var1) {
if (this.tryReleaseShared(var1)) {
this.doReleaseShared();
return true;
} else {
return false;
}
}
//对当前线程锁状态进行减操作,判断锁的重入次数是否全部释放完成。
protected final boolean tryReleaseShared(int var1) {
Thread var2 = Thread.currentThread();
int var4;
if (this.firstReader == var2) {
if (this.firstReaderHoldCount == 1) {
this.firstReader = null;
} else {
--this.firstReaderHoldCount;
}
} else {
ReentrantReadWriteLock.Sync.HoldCounter var3 = this.cachedHoldCounter;
if (var3 == null || var3.tid != ReentrantReadWriteLock.getThreadId(var2)) {
var3 = (ReentrantReadWriteLock.Sync.HoldCounter)this.readHolds.get();
}
var4 = var3.count;
if (var4 <= 1) {
this.readHolds.remove();
if (var4 <= 0) {
throw this.unmatchedUnlockException();
}
}
--var3.count;
}
int var5;
do {
var5 = this.getState();
var4 = var5 - 65536;
} while(!this.compareAndSetState(var5, var4));
return var4 == 0;
}
//锁状态减为0之后,判断头结点是否为空,如果不为空,且结点的等待状态为-1,则需uppark下一个结点,改变节点的状态。如果没有下一个结点,头结点保持为空
private void doReleaseShared() {
while(true) {
AbstractQueuedSynchronizer.Node var1 = this.head;
if (var1 != null && var1 != this.tail) {
int var2 = var1.waitStatus;
if (var2 == -1) {
//把节点状态从-1改为0,如果改变失败,就重试。之后,唤醒头结点的后继结点。(唤醒在parkAndCheckInterrupt()中阻塞的线程)
if (!compareAndSetWaitStatus(var1, -1, 0)) {
continue;
}
this.unparkSuccessor(var1);
} else if (var2 == 0 && !compareAndSetWaitStatus(var1, 0, -3)) {
continue;
}
}
if (var1 == this.head) {
return;
}
}
}
//打断阻塞在这的线程。在tryAcquireShared中获得锁,由于不是写锁,也没超上限,所以将节点状态由0改为1,返回1。setHeadAndPropagate 将坐在的节点设置为头结点,
private void doAcquireShared(int var1) {
AbstractQueuedSynchronizer.Node var2 = this.addWaiter(AbstractQueuedSynchronizer.Node.SHARED);
boolean var3 = true;
try {
boolean var4 = false;
while(true) {
AbstractQueuedSynchronizer.Node var5 = var2.predecessor();
if (var5 == this.head) {
int var6 = this.tryAcquireShared(var1);
if (var6 >= 0) {
this.setHeadAndPropagate(var2, var6);
var5.next = null;
if (var4) {
selfInterrupt();
}
var3 = false;
return;
}
}
if (shouldParkAfterFailedAcquire(var5, var2) && this.parkAndCheckInterrupt()) {
var4 = true;
}
}
} finally {
if (var3) {
this.cancelAcquire(var2);
}
//如果头结点的下一个节点为空,或者也是共享状态,那么继续释放共享锁。这也是可以读读并发的原因
private void setHeadAndPropagate(AbstractQueuedSynchronizer.Node var1, int var2) {
AbstractQueuedSynchronizer.Node var3 = this.head;
this.setHead(var1);
if (var2 > 0 || var3 == null || var3.waitStatus < 0 || (var3 = this.head) == null || var3.waitStatus < 0) {
AbstractQueuedSynchronizer.Node var4 = var1.next;
if (var4 == null || var4.isShared()) {
this.doReleaseShared();
}
}
}