java.util.concurrent.locks.Reent
什么是重入锁
重入锁就是指重复进入锁,它表示该锁能够支持一个线程对资源的重复加锁。
重入锁继承与实现关系
public class ReentrantLock implements Lock, java.io.Serializable
重入锁的自定义同步器源码
重入锁ReentrantLock采用继承的AQS的自定义的同步器来实现的。在自定义同步器的基础上实现的公平锁和非公平锁的。
abstract static class Sync extends AbstractQueuedSynchronizer
自定义同步器实现方法
nonfairTryAcquire() 方法:在独占模式、非公平模式下尝试获取同步状态
/**
* 在独占模式,非公平模式下尝试获取同步状态
*/
final boolean nonfairTryAcquire(int acquires) {
//获取当前的线程current
final Thread current = Thread.currentThread();
//原子的方式获取当前同步状态值
int c = getState();
//如果同步状态值为初始化状态
if (c == 0) {
//如果当前同步状态值等于预期值0
//那么就将以原子的方式将当前同步状态值设置为更新值acquires
if (compareAndSetState(0, acquires)) {
//设置拥有独占访问权限的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果当前线程为拥有独占访问权限的线程,
//这个代码块是当前线程重入情况下,同步状态值增加acquires
else if (current == getExclusiveOwnerThread()) {
//当前同步状态值加上获取对象本身的状态值
int nextc = c + acquires;
//如果最新同步状态值小于0,抛出最大锁数量超出
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//原子的方式设置最新同步状态值
setState(nextc);
return true;
}
return false;
}
步骤解析:
<1>首先获取当前线程,如果当前同步状态值为初始化状态,就设置初始状态值(acquires值),设置独占访问线程的权限为当前线程,返回true。
<2>如果当前同步状态值已经被设置过了并且独占访问线程的权限就是当前线程(锁重入的情况了),那么就将原本的同步状态值加上锁重入对象的状态值作为最新同步状态值,然后设置同步状态值,返回true。
<3>如果未设置成功并且当前线程不是获取锁的线程(独占式嘛),就返回false了。
nonfairTryAcquire 流程图:
tryRelease() 方法:在独占模式下,尝试释放同步状态。
//在独占模式下,尝试释放同步状态
protected final boolean tryRelease(int releases) {
//原子方式获取当前同步状态值减去对象本身已经获取要释放的状态值
int c = getState() - releases;
//如果当前线程不是拥有独占访问权限的线程,抛出非法主机状态异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//释放标志位默认为false
boolean free = false;
//如果释放了的同步状态值为初始化状态
if (c == 0) {
//设置释放标志位为true
free = true;
//设置拥有独占访问权限的线程为null
setExclusiveOwnerThread(null);
}
//原子的方式更新最新的同步状态值
setState(c);
//返回未完全释放状态值的标志位false
return free;
}
步骤解析:
<1> 获取释放状态值之后得到最新的同步状态值 c。
<2> 如果释放同步状态值为初始化状态(说明没有获取锁资源的线程了),则设置释放标志为true,设置拥有独占访问权限的线程自然也就是null了。
<3>更新同步状态值为最新的同步状态值 c,返回释放标志位的值。
其他方法
//当前的线程是否是以独占的方式进行的
protected final boolean isHeldExclusively() {
//拥有独占访问权限的线程与当前线程比较,是否为同一个
return getExclusiveOwnerThread() == Thread.currentThread();
}
//获取当前同步状态值
final int getHoldCount() {
//如果当前线程是以独占的方式运行,则得到当前同步状态值,否则为0(初始化的值)
return isHeldExclusively() ? getState() : 0;
}
什么是公平锁和非公平锁
如果对锁先请求的线程先被满足就是公平锁,而对锁先请求没有得到满足,满足条件是随机的就是不公平锁。这么一想,那么对于公平锁,等待时间最长的线程最有可能获取锁,所以由此也会看出公平锁是有顺序获取的。
非公平锁的源码
非公平锁的继承与实现关系
// 独占模式下公平锁的实现
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
}
非公平锁的tryAcquire方法:在独占模式下获取对象的状态
// 在独占模式下,公平锁的方式下获取对象状态
protected final boolean tryAcquire(int acquires) {
//这个是继承自抽象父类的方法,前面已介绍
return nonfairTryAcquire(acquires);
}
非公平锁的lock方法:获取锁
// 在非公平的方式下获取锁
final void lock() {
// 如果当前线程持有的同步状态值为初始化状态
// 那么将更新当前的同步状态值为1,证明获取锁的线程加锁次数为1
if (compareAndSetState(0, 1))
//设置当前线程为独占线程
setExclusiveOwnerThread(Thread.currentThread());
else//否则从同步队列中获取线程对象
acquire(1);
}
公平锁的源码
公平锁的继承与实现关系
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
}
公平锁的tryAcquire方法:在独占模式下,公平锁的方式下获取对象状态
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//原子的方式获取当前同步状态值
int c = getState();
//如果当前同步状态值为0,也就是初始化状态
if (c == 0) {
//如果不存在等待时间更长的线程(就是同步队列中当前节点是否有前驱节点)
//并且当前同步状态值为初始化状态(更新同步状态值为acquires)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//设置拥有独占访问权限的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果拥有独占访问权限的线程是当前线程
else if (current == getExclusiveOwnerThread()) {
//计算最新的同步状态值
int nextc = c + acquires;
//如果最新的同步状态值小于0,那么抛出超出最大锁的数量
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//原子的方式设置最新的同步状态值
setState(nextc);
return true;
}
return false;
}
}
tryAcquire 流程图:
公平锁的lock方法:在公平方式下,获取锁
//独占模式,当前线程获取公平锁
final void lock() {
//公平锁获取锁的方式,就是直接按照同步队列的顺序获取对象,不管他有没有新的线程来
acquire(1);
}
ReentrantLock重入锁的构造方法
/**
* 实例化一个独占模式下的非公平锁的对象
* 默认构造方法是采用非公平锁方式
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* 如果传入的参数fair为true就创建公平锁,否则就创建非公平锁
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
重入锁的方法
lock 方法:获取锁
public void lock() {
sync.lock();
}
tryLock 方法:
/**
* 查看该锁没有对其他线程持有,是则返回true,否则为false
*/
public boolean tryLock() {
//自定义同步器的非公平方式获取同步状态
return sync.nonfairTryAcquire(1);
}
unlock 方法:
/**
* 尝试释放锁
*/
public void unlock() {
sync.release(1);
}
阅读总结
(1)ReentrantLock采用自定义同步器继承AQS的方式来实现的。
(2)ReentrantLock分为公平锁和非公平锁两种获取锁的方式。
(3)ReentrantLock中线程重进入的过程
① 判断获取锁的线程是否为当前占据着锁的线程。
② 如果①确定是当前线程,计算当前的同步状态值(之前占据着锁的线程的同步状态值加上重进入的当前线程对象的同步状态值),将计算完的同步状态值以CAS的方式更新为当前的同步状态值。
(4)ReentrantLock中释放锁的过程
① 通过CAS的方式获取当前同步状态值减去要释放的同步状态值,如果减完结果为0就释放成功,否则释放失败。
② 释放锁成功的条件就是最终的同步状态值为0。
(5)ReentrantLock中公平锁和非公平锁在获取锁的区别
① 公平锁:完全就是按照AQS中同步队列的顺序(出队获取锁的顺序从头到尾,先进先出),头节点的线程先获取锁然后是后继节点,依次获取。
② 非公平锁: 首先使用CAS方式判断当前的线程是否处于初始化状态,如果是就设置为独占线程,否则从AQS同步队列中获取头节点线程。
③ 区别:公平锁完全就是按照AQS同步队列顺序,线程获取锁。但是非公平锁会考虑新线程的到来,如果新线程到来了,那么就直接让新线程作为当前获取锁的线程,就没AQS同步队列中那么线程节点啥事了。。。
---------------------------该源码为jdk1.7版本的
闲话:
最近深入详细的读了AQS和重入锁,它们的区别是什么?
AQS主要就是同步队列和等待队列之间来回移动元素。至于获取独占锁还是共享锁都是基于同步状态值state来判断的。而重入锁的自定义同步器只是继承了AQS,锁重入的方式就是判断当前维护锁的线程是否为当前请求锁的线程,是就同步状态值加1。