ReentrantLock是Lcok默认实现方式之一,它是基于AQS(AbstractQueuedSynchronizer 队列同步器)实现的,它默认是通过非公平锁实现的,在它的内部有一个state的状态字段用于表示锁是否被占用,如果是0则表示未被占用,此时线程就可以把state改成1,并成功获得了锁,而其他未获得锁的线程只能去排队等待获取资源。
首先来看下ReentrantLock的两个构造函数:
public ReentrantLock() {
sync = new NonfairSync();//非公平锁
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
无参的构造函数创建了一个非公平锁,用户也可以根据第二个构造方法,设置一个boolean类型的值,来决定是否使用公平锁来实现现成的调度。
看下公平锁源码:
//可以看到该方法就是以独占的方式获取锁,获取成功后返回true。从这个方法可以看出state变量是实现可重入性的关键。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
//调用的是AQS里面的acquire方法 ,具体AQS这个方法的源码分析见博文末尾的连接
acquire(1);
}
//在AQS中这个方法是一个空实现
protected final boolean tryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 获取state状态,0表示未锁定,大于1表示重入
int c = getState();
if (c == 0) {
// hasQueuedPredecessors查看队列中是否有比它等待时间更久的线程
//公平锁比非公平锁多了这行代码
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 没有比当前线程等待更久的线程了,通过CAS的方式修改state
// 成功之后,设置当前拥有独占访问权的线程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 独占访问权的线程就是当前线程,重入
// 此处就是【可重入性】的实现
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 直接修改state
setState(nextc);
return true;
}
return false;
}
}
再看下非公平锁源码:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//公平锁中没有这一个if判断,该方法是尝试将state 的值由0改成1,设置成功则说明
当前没有其他线程持有该锁,不用再去排队了。否则 则需要acquire方法去排队
if (compareAndSetState(0, 1))
//将当前线程设置为此锁的持有者
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//非公平锁跟公平锁就这一行不一样。非公平锁没有hasQueuedPredecessors()方法
//不需要查看队列中是否有比它等待时间更久的线程
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;
}
由上面代码可见:公平锁是需要按照请求的顺序来获得锁 先进先出;而非公平锁则允许"插队"的情况存在,所谓的"插队"指的是,线程在发送请求的同时该锁的状态恰好变成了可用,那么此线程就可以跳过队列中所有排队的线程直接拥有该锁。公平锁由于有挂起和恢复所以存在一定的开销,因此性能不如非公平锁。
ReentrantLock源码
ReentrantLock和synchronized使用的场景是什么,实现机制有什么不同
ReentrantLock和synchronized都是独占锁
synchronized:
1、是悲观锁会引起其他线程阻塞,java内置关键字,
2、无法判断是否获取锁的状态,锁可重入、不可中断、只能是非公平
3、加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单但显得不够灵活
4、一般并发场景使用足够、可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁5、可以修饰方法、修饰代码块
ReentrantLock:
1、是个Lock接口的实现类,是悲观锁,
2、可以判断是否获取到锁,可重入、可中断、可公平可不公平
3、需要手动加锁和解锁,且 解锁的操作尽量要放在finally代码块中,保证线程正确释放锁
4、在复杂的并发场景中使用在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致 其他线程无法获得该锁。
5、创建的时候通过传进参数true创建公平锁,如果传入的是false或没传参数则创建的是非公平锁
6、底层不同是AQS的state和FIFO队列来控制加锁7、只能修饰代码块