重入锁ReentrantLock
顾名思义:支持可重入的锁,synchronized以实支持重入的锁。它表示能够支持一个线程重复获取同一个锁对象或者说能够支持同一个线程对资源的重复加锁。
同时ReentrantLock还支持公平锁和非公平锁的选择,默认为非公平锁,公平锁的效率要比非公平锁的效率低。
公平锁
当同步队列中有多个线程在等待时,等待时间最长的线程优先获得锁。
非公平锁
公平锁的对立就非公平锁,即不管等待时间的长短。
实现重入锁的逻辑
以公平锁为例主要思想为:
- 线程再次获取锁。锁需要去识别当前想要获取锁的线程是否为当前已经获取锁的线程,如果是则获取锁成功
- 锁的最终释放。线程重复获取了n次锁,随后在第n次释放锁之后,其他线程可以获取到该锁,因此在加锁的时候需要计数;释放的时候计数自减,当计数等于0的时候表示锁已经完全释放。
ReentrantLock通过组合自定义同步器来实现锁的获取于释放的。
**
NonfairSync#lock
final void lock() {
if (compareAndSetState(0, 1))//尝试获取锁
//获取成则将当前线程设置为独占锁的持有线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
复制代码
最终调用到Sync#nonfairTryAcquire(int acquires),此处为重入锁的语义实现
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前锁的状态
int c = getState();
if (c == 0) {//如果是初始状态,说明还没有线程获取到锁
//执行CAS加锁操作
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//如果当前线程是获取了独占锁的线程,(重入锁)
//将state计数累加
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果不是重入锁,返回false
return false;
}
复制代码
释放锁逻辑
//释放锁,公平锁和非公平锁都使用这一个
//Sync自定义同步器内部类实现
protected final boolean tryRelease(int releases) {
//因加锁时导致状态计数累加了,因此释放锁时需要减回来
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
//如果当前释放锁的线程不是当前获得独占锁的线程,则抛出异常
throw new IllegalMonitorStateException();
boolean free = false;
//如果状态值不等于0就一直返回false
if (c == 0) {
//针对重入的线程,需要累减,当状态值减为0才说明该线程获取的锁全部释放了
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
复制代码
公平锁与非公平锁的区别
区别就是多了一步判断当前节点是否为同步队列的第一个。
//公平锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
//在获取锁的时候判断了当前节点是否有前驱节点,如果有前驱节点则返回true;如果当前节点为头节点或者当前队列为空返回false,可以获得锁
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
复制代码
测试公平锁与非公平锁代码见:gitee
测试结果
非公平锁:
公平锁:
**
由结果可见,非公平锁可能会出现刚刚释放锁的线程又获取到锁的情况,使得其他线程只能在同步队列中等待,如果把每次不同线程获取到锁定义为1次切换,那么公平锁的切换次数显然比非公平锁多。
与synchronize的区别
- ReentrantLock提供了更多、更加全面的功能,具备更强的扩展性。如:时间锁等候、可中断锁等候
- ReentrantLock还提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活
- ReentrantLock提供了可轮询的锁请求,它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时再处理,而synchronized则一旦进入锁请求要么成功要么阻塞,ReentrantLock更不容易产生死锁
- ReentrantLock支持更加灵活的同步代码快,但是使用synchronized时,只能在同一个synchronized块结构中获取和释放。ReentrantLock的锁释放一定要在finally中处理,否则可能会产生严重后果。
- ReentrantLock支持中断处理,且性能较synchronzied会好些