之前看了ReentrantLock独占锁和Semaphore共享锁的原理和介绍,结合这两个锁的就是ReentrantReadWriteLock读写锁。
读写锁就是:读读共享,读写互斥,写读互斥,写写互斥。
读写锁的特性:
1、支持公平与非公平锁的获取
如: public ReentrantReadWriteLock(boolean fair)//默认是非公平的
2、重进入:如:当一个读线程获取了读锁之后能够再次获取到读锁
3、锁降级:
锁降级就有必要说说了。锁降级指的是:写锁降级为读锁。指把持住当前拥有的写锁,再获取到读锁,锁喉释放写锁的过程,这时只拿了一个读锁,这个过程就是锁降级
但是:::我们看下代码:
(在图片中我说了下我的疑虑和如何实现锁降级,以及自己的分析)
那么它的内部实现是怎么实现的呢?其实也是对状态state做修改,当阻塞的时候会将被阻塞的没有拿到锁的放到同步队列中。
1、我们先说下它的原理--是如何既能保存写状态也能保存读状态的。
读写锁同样依赖于自定义同步器来实现同步功能,而读写转态就是同步器的同步状态。
我们的同步状态是一个int类型的,我们需要按位分割使用这个变量。读写锁分成两部分:高16位是读,低16位是写
设:当前状态为S
当低16位有值时,说明存在写锁。当写状态+1的时候:S+1 (存在写锁,又加1,说明是当前线程写锁重入)
当高16中不全为0时,说明存在读锁,当读状态+1的时候。高16位加1就是 1<<16,(1是低位即:0000000000000001 ,将1向左移动16位,),那么最终的S值为 S+(1<<16)
下面我们来看下源码:
写锁的获取与释放:
这里我们贴一下addWaiter和acquireQueued的源码
写锁的释放:
-------------------------------------下面我们看下读锁---------------------------------------------------
读锁就是我们之前所说的共享锁,这里只不过我们与写锁共用了一个状态变量。下面看下他的实现
获取锁的逻辑如下:
上图中的:fullTryAcquireShared(current)-->读取的完整版本,用于处理CAS缺失和可重入读取
里面是一个死循环,重新尝试获取锁,包含了锁降级和锁重入的实现。
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
for (;;) {
int c = getState();
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 != current.getId()) {
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 != current.getId())
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
上图我们在Semaphore共享锁的实现中也说过,代码逻辑是一样的。
这里想说下我昨天刚刚遇到的一个场景,用到的是共享锁,--Semaphore。
场景:某系统有一个通过上传文件同步数据的功能,我们想控制一下,当用户上传少于5个的时候我们就直接让其执行,并显示状态为正在执行中,当上传数大于5个的时候,比如是N,那么N-5的文件,就需要显示状态为等待状态,当有一个上传成功或失败后,就需要从等待队列中取一个,变为正在执行中。这么一个需求;
当时我就想到了Semaphore来用信号量控制,但是boss非得给我说创建两个队列,一个是等待队列,一个是执行队列,我们还得搞个Map或者List容器存所有的上传任务。来实现云云的。当然按照他说的逻辑也能实现,但是实现起来也是有一定难度的,考虑的东西很多,而且是按用户区别的,就是,当另一个用户操作时,并不受第一个用户的影响,但是也要实现这样的功能,,这样就有套了一层,我想了一天,觉得他那种还是比较麻烦,就用了Semaphore.
原因:其实Semaphore就是控制并发线程数量的,其内部已经实现了等待队列阻塞,而且还是线程安全的,对于上面的问题我们就不要考虑那么多。省了90%的工作量。我们只需要在每个用户调用该功能的时候就为每个用户创建一个Semaphore信号量,可以公平也可以非公平,每次只能获取1个许可,一共5个许可。当该用户再次来获取的时候,我们直接从Map里取一下之前创建的Semaphore信号量就可以了。