思考题
- 什么是可重入,什么是可重入锁? 它用来解决什么问题?
- ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。
- ReentrantLock是如何实现公平锁的?
- ReentrantLock是如何实现非公平锁的?
- ReentrantLock默认实现的是公平还是非公平锁?
- 使用ReentrantLock实现公平和非公平锁的示例?
- ReentrantLock和Synchronized的对比?
- 为啥ReentrantLock默认要是非公平的锁呢?
- 什么时候用ReentrantLock,什么时候用Synchronized呢?
一、简介
1、ReentrantLock类是可重入锁,实现了Lock接口,所以,有lock和unlock方法可以使用。
2、ReentrantLock里边有三个内部类:Sync、NonfairSync、FairSync
Sync继承了AQS类,NonfairSync和FairSync都是Sync的子类。ReentrantLock根据这两个可以分别实现公平锁和非公平锁
3、对于ReentrantLock的操作,主要是涉及到AQS类的方法。AQS类中的state字段,是ReentrantLock锁可重入的核心。state字段默认是0,表示对象未被线程获取,大于0的次数表示被一个线程多次获取的次数。可重入的特性是state字段不仅仅是0和1,还可以是大于1的数值!
4、ReentrantLock类通过重写AQS同步器的:getState、setState和compareAndSetState方法来原子的操作state字段。
二、三个内部类的源码
Sync类
abstract static class Sync extends AbstractQueuedSynchronizer { //继承了AQS
// 序列号
private static final long serialVersionUID = -5179523762034025860L;
// 获取锁
abstract void lock();
// 非公平方式获取锁
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 获取状态,记录的是锁重入的次数
int c = getState();
if (c == 0) { // 表示没有线程正在竞争该锁
if (compareAndSetState(0, acquires)) { // 比较并设置状态成功,状态0表示锁没有被占用
// 设置当前线程独占
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;
}
// 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程不为独占线程
throw new IllegalMonitorStateException(); // 抛出异常
// 释放标识
boolean free = false;
if (c == 0) {
free = true;
// 已经释放,清空独占
setExclusiveOwnerThread(null);
}
// 设置标识
setState(c);
return free;
}
// 判断资源是否被当前线程占有
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
// 新生一个条件
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
// 返回资源的占用线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 返回状态
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// 资源是否被占用
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
// 自定义反序列化逻辑
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
NonfairSync类
// 非公平锁
static final class NonfairSync extends Sync {
// 版本号
private static final long serialVersionUID = 7316153563782823691L;
// 获得锁
final void lock() {
if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用
// 把当前线程设置独占了锁
setExclusiveOwnerThread(Thread.currentThread());
else // 锁已经被占用,或者set失败
// 以独占模式获取对象,忽略中断
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
FairSyn类
// 公平锁
static final class FairSync extends Sync {
// 版本序列化
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 以独占模式获取对象,忽略中断
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 尝试公平获取锁
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
if (c == 0) { // 状态为0
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
// 设置当前线程独占
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据
// 下一个状态
int nextc = c + acquires;
if (nextc < 0) // 超过了int的表示范围
throw new Error("Maximum lock count exceeded");
// 设置状态
setState(nextc);
return true;
}
return false;
}
}
三、ReentrantLock源码
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final Sync sync; //AQS同步器子类实例
public ReentrantLock() { //无参构造,默认是非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) { //带参构造,根据Boolean值确定公平与否
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() { //lock--》AQS的lock方法---》unsafe类的底层lock
sync.lock();
}
核心源码
//上锁时如果线程被打断,则中断当前方法
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//尝试获取锁,且是非公平的
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
//尝试获取锁,当过程中遇到超时或被打断,则中断当前方法
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
//释放锁,调用的是ReentrantLock的内部类的Sync类的重写AQS类的tryRelease方法,涉及到state的值减一
public void unlock() {
sync.release(1);
}
//创建一个条件对象
public Condition newCondition() {
return sync.newCondition();
}
//得到 state的值,state的值默认是0,随着线程的重入加一
public int getHoldCount() {
return sync.getHoldCount();
}
//判断是否被当前线程独占
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
//就是检查state的值,如果为0,表示未被线程占用,否则,被占用,也就是上锁了
public boolean isLocked() {
return sync.isLocked();
}
//判断是否为公平锁
public final boolean isFair() {
return sync instanceof FairSync;
}
//得到当前占有的线程
protected Thread getOwner() {
return sync.getOwner();
}
//查询是否有线程正在等待获取此锁
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
//查询给定线程是否正在等待获取此锁
public final boolean hasQueuedThread(Thread thread) {
return sync.isQueued(thread);
}
//返回 线程等待去获取这个锁的大概数量
public final int getQueueLength() {
return sync.getQueueLength();
}
//返回一个可能去等待尝试获取这个锁的线程集合
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
//查询是否有任何线程正在等待与此锁关联的给定条件
public boolean hasWaiters(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}
//返回在条件队列中与此锁关联的大概的线程数
public int getWaitQueueLength(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}
//返回一个集合,该集合包含可能存在的线程正在等待与此锁关联的给定条件
protected Collection<Thread> getWaitingThreads(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}
总结:ReentrantLock类全部是通过AQS子类实例来操作。其中AQS子类Sync继承AQS,重写AQS的一些方法和新增自己的一些方法,使之满足ReentrantLock类的操作!所以,本质上,任然是AQS类在操作锁!
四、思考题解答
1、什么是可重入,什么是可重入锁? 它用来解决什么问题?
可重入是指一个对象的锁,这个锁对应有个锁计数器,加锁和释放锁对应的指令是 monitorenter和monitorexit。
执行monitorenter使锁计数器加一,monitorexit使锁计数器减一。
锁计数器默认为0,表示现在没有被任何线程使用,当线程A进来获取锁,那么,执行monitorenter指令,同时,锁计数器加一,当前值为1。在线程A的管程中,有需要用到该对象,此时,不需要再重新获取对象的锁,而是锁计数器直接加一操作即可,这就是可重入性。
可重入锁是一个线程在第一次获取资源时,才需要获取锁,后续操作中,再次用到锁的时候,不需要重新获取锁(acquire),而是直接state变量加一操作即可。
它可以用来解决同一个线程不断的获取锁和释放锁的问题,我们知道,频繁的获取锁和释放锁是很消耗性能的。
2、ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。
ReentrantLock有个内部类Sync,它继承了AQS,同时,ReentrantLock还有另外两个内部类,一个NonfairSync,另一个FairSyn,他们都是继承Sync,分别表示非公平锁和公平锁
3、ReentrantLock是如何实现公平锁和非公平锁的?
首先,公平与否是在获取锁上进行的体现。
公平锁会获取锁时会判断同步队列里是否有线程再等待,若有获取锁就会失败,并且会加入同步队列。
非公平锁获取锁时不会判断阻塞队列是否有线程再等待,所以对于已经在等待的线程来说是不公平的,但如果是因为其它原因没有竞争到锁,它也会加入阻塞队列。
进入同步队列的线程,竞争锁时都是公平的,应为队列为先进先出(FIFO)
公平锁的实现原理在FairSyn内部类中
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
if (c == 0) { // 状态为0
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
// 设置当前线程独占
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据
// 下一个状态
int nextc = c + acquires;
if (nextc < 0) // 超过了int的表示范围
throw new Error("Maximum lock count exceeded");
// 设置状态
setState(nextc);
return true;
}
return false;
}
比较NonfairSync类中的非公平锁,源码在Sync类中,原因是ReentrantLock默认是非公平锁
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;
}
小结:两个方法的不同点在于公平锁实现的同步器,线程在进来获取锁时,会查一下同步队列中是否有正在等待的线程,有,则自己弄到同步队列的尾部,让等待在同步队列中的线程获取锁。
非公平锁实现的同步器,线程进来后获取锁,它不会判断同步队列中是否有正在等待锁的线程,而是直接自己尝试获取锁,如果成功,则执行代码,不成功,则放入到同步队列的尾部,这样,不符合同步队列的FIFO操作,也对正在等待的线程不公平。
4、ReentrantLock和Synchronized的对比?
① 底层实现上来说,synchronized 是JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter与monitorexit),对象只有在同步块或同步方法中才能调用wait/notify方法,ReentrantLock 是从jdk1.5以来(java.util.concurrent.locks.Lock)提供的API层面的锁。
synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向OS申请重量级锁,ReentrantLock实现则是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile保证数据可见性以实现锁的功能。
② 是否可手动释放:
synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用; ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock()和unlock()方法配合try/finally语句块来完成,使用释放更加灵活。
③ 是否可中断
synchronized是不可中断类型的锁,除非加锁的代码中出现异常或正常执行完成; ReentrantLock则可以中断,可通过trylock(long timeout,TimeUnit unit)设置超时方法或者将lockInterruptibly()放到代码块中,调用interrupt方法进行中断。
④ 是否公平锁
synchronized为非公平锁 ReentrantLock则即可以选公平锁也可以选非公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁。
⑤ 锁是否可绑定条件Condition
synchronized不能绑定; ReentrantLock通过绑定Condition结合await()/singal()方法实现线程的精确唤醒,而不是像synchronized通过Object类的wait()/notify()/notifyAll()方法要么随机唤醒一个线程要么唤醒全部线程。
⑥ 锁的对象
synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。
5、为啥ReentrantLock默认要是非公平的锁呢?
公平是好事,不公平是不好的,不是吗?(当孩子们想要一个决定时,总会叫嚷“这不公平”。我们认为公平非常重要,孩子们也知道。)在现实中,公平保证了锁是非常健壮的锁,有很大的性能成本。要确保公平所需要的记帐(bookkeeping)和同步,就意味着被争夺的公平锁要比不公平锁的吞吐率更低。作为默认设置,应当把公平设置为 false ,除非公平对您的算法至关重要,需要严格按照线程排队的顺序对其进行服务。
6、什么时候用ReentrantLock,什么时候用Synchronized呢?
使用Synchronized:java.util.concurrent.lock 中的锁定类是用于高级用户和高级情况的工具 。一般来说,除非您对 Lock 的某个高级特性有明确的需要,或者有明确的证据(而不是仅仅是怀疑)表明在特定情况下,同步已经成为可伸缩性的瓶颈,否则还是应当继续使用 synchronized。
使用ReentrantLock:在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。
五、总结
1、ReentrantLock实现了Lock接口,意味着有Lock的特性,比如:可中断操作,必须手动关闭锁,获取锁有返回值等。
2、ReentrantLock有三个内部类,Sync、FairSync和NonFairSync三个,Sync继承了AQS,FairSync和NonFairSync继承了Sync,分别实现了公平锁和非公平锁,类似于semaphore信号量类。
3、公平锁和非公平锁体现在获取锁时,和semaphore类也是一样的原理:如果是公平锁,一个线程进来后,判断这个线程是否为同步队列中的第一个节点,否则,将当前线程作为最后一个节点放入同步队列尾部。而非公平锁相反,一个线程进来后,首先去争取锁对象,而不会判断这个线程是否为同步队列的第一个元素。
4、ReentrantLock的核心其实就是对继承的AQS类的state属性的操作,这个state属性不仅仅是0和1了,由于可以重入,导致state的值可以大于等于0,每次线程重入则加一。且对这个state属性的值的操作也是通过重写了AQS类的三个方法:getState、setState和compareAndSetState。延展一下:对于一般的锁,都会实现AQS类,对state属性进行操作,也相应的需要重写getState、setState和compareAndSetState这三个方法!