前一篇讲到synchronized,同样是锁 和Lock有什么区别吗?
1 synchronized 是通过jvm实现,Lock 通过cas
2 synchronized 悲观锁,Lock乐观锁
3 用法不一样,Lock需要手动关闭(正是因为手动关闭,才使得Lock更加的灵活)
有些逻辑,还是得使用Lock灵活的关闭才能实现 ,而synchronized 实现不了
ReentrantLock:只要调用了lock,如果资源被占用,就会阻塞
ReentrantReadWriteLock(读写锁):
线程进入读锁的前提条件:
1 没有其他线程的写锁,
2没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
线程进入写锁的前提条件:
1 没有其他线程的读锁
2 没有其他线程的写锁
我们先来看看ReentrantLock
ReentrantLock分FairSync(公平)和NonfairSync(不公平),默认是不公平的(相对而言,性能会好点),通过AQS实现同步锁,所以大量的代码都在AQS里了
AQS分析
public ReentrantLock() {
sync = new NonfairSync();
}
//可以通过fair参数设置模式
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
来看下我们常用的lock方法,即加锁,下面用的是非公平模式
1 如果设置状态成功,则设置占用线程为当前线程
2 不成功
当state==0 则调用nonfairTryAcquire方法再次通过cas尝试获取资源
当state>0并且资源为当前线程占有,则设置state为state+acquires
如果是公平模式,则是只有队列是空的时候 ,才会用当前线程去获取资源,FIFO,先进先出
public void lock() {
sync.lock();
}
//这里就是公平和不公平的区别
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
释放其实调用的就是AQS的release,这里就不介绍了。
小结:
ReentrantLock当然不止这么点功能,还有tryLock(返回true 或者false),getOwner(获取当前线程),newCondition(new 一个条件,通过await阻塞,signal释放,这其实也是AQS里的功能)
至于ReentrantReadWriteLock读写来说,内部有ReadLock(读锁)和WriteLock(写锁),我们需要分别取调用writeLock和readLock返回对应的锁进行lock
ReentrantReadWriteLock将这个int型state变量分为高16位和低16位,高16位表示当前读锁的占有量,低16位表示写锁的占有量
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
读锁
获取资源
//用于记录每个线程获取到的锁的数量
//使用id和count记录
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
//这里使用了ThreadLocal为每个线程都单独维护了一个HoldCounter来记录获取的锁的数量
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
//获取到的读锁的数量
private transient ThreadLocalHoldCounter readHolds;
//最后一次成功获取到读锁的线程的HoldCounter对象
private transient HoldCounter cachedHoldCounter;
//第一个获取到读锁的线程
private transient Thread firstReader = null;
//第一个获取到读锁的线程拥有的读锁数量
private transient int firstReaderHoldCount;
1 当前被其他线程的写锁占有 则返回-1
2 如果队列中是空的,并且读锁占有数量小于最大值,并且cas成功
2.1 如果读锁占有数量=0 说明读锁是空 的,则给赋值给读线程并且个数设置为1
2.2如果读锁占有数量>0 且占有线程为当前线程 则 直接个数+1
2.3 如果资源被其他线程占有 则 将当前获取当前线程的HoldCounter 并且个数+1
3 步骤2 有问题 则 执行fullTryAcquireShared
4 如果返回为-1 则入队列等待
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//步骤1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
//步骤2
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//步骤2.1
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {//步骤2.2
firstReaderHoldCount++;
} else {//2.3
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;
}
//步骤3
return fullTryAcquireShared(current);
}
fullTryAcquireShared
因为比较长,直接注解了
//主要是针对!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT) 条件
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
//当前被其他线程的写锁占有 则返回-1
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {//读锁被阻塞
if (firstReader == current) {
} else {
//循环第一次 如果当前锁 如果当前线程的读锁为0就remove
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");
//cas成功
if (compareAndSetState(c, c + SHARED_UNIT)) {
//下面tryAcquireShared差不多了
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;
}
return 1;
}
}
}
释放资源
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//如果为当前资源
if (firstReader == current) {
//如果写锁线程只有一个 则将当前写锁线程设置为null,否则-1
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
//如果当前线程不是写锁占有线程 则找到对应的HoldCounter -1
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;
//cas设置
if (compareAndSetState(c, nextc))
//设置成功 重置为0
return nextc == 0;
}
}
写锁
获取资源
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
//获取写锁状态
int w = exclusiveCount(c);
if (c != 0) {
//c不为0 w==0 表示有读锁但是没有写锁
//占有资源的线程不是当前线程 则return false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//超出抛异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
释放资源基本和ReentrantLock一样 所以就不贴代码了
总结:
1 读锁的重入是允许多个申请读操作的线程的,而写锁同时只允许单个线程占有,该线程的写操作可以重入。
2 如果一个线程占有了写锁,在不释放写锁的情况下,它还能占有读锁,即写锁降级为读锁。
3 对于同时占有读锁和写锁的线程,如果完全释放了写锁,那么它就完全转换成了读锁,以后的写操作无法重入,在写锁未完全释放时写操作是可以重入的。