您的位置:首页 > 编程语言 > Java开发

【JDK源码分析】通过源码彻底理解ReentrantLock显示锁

2018-07-24 20:52 676 查看
前言
ReentrantLock和synchronized一样是一个可重入的互斥锁,但ReentrantLock功能更强大,它提供了非公平和公平两种锁争用策略供使用者选择,而synchronized只有非公平一种。ReentrantLock提供了可中断的锁等待机制以及可用于多组线程需要分组唤醒的条件。

类图
下面是ReentrantLock的类图,内部抽象类Sync继承了AbstractQueuedSynchronizer(以下简称AQS),公平锁FairSync、非公平锁NonfairSync继承了抽象类Sync。
ReentrantLock

源码
ReentrantLock类属性和构造器
先看ReentrantLock的属性,属性只有sync,可见由它实现了整个类的功能。公平锁是所有线程都按FIFO的方式进行,获取锁释放锁的顺序进行;而非公平锁则是在锁释放的时候,不会限制新来的线程进行锁的争用。

复制代码
private final Sync sync;

//默认构造器,生成的是公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 有参构造器,形参fair为true则构造公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

复制代码
ReentrantLock类主要方法
下面以ReentrantLock的主要方法的调用过程来一步步分析源码

lock 方法
lock方法调用了内部类Sync的lock方法

public void lock() {
sync.lock();
}
lock方法为抽象方法

abstract void lock();

我们先看lock方法的非公平实现

1.1 lock方法的非公平实现
lock方法大量使用了其祖父类AQS中的方法,这里补充几点:

AQS有个state变量,用于表示锁状态,为0表示处理无锁状态,>0表示处理有锁状态;
compareAndSetState为AQS提供操作state变量的CAS原子方法;
关于详细的AQS的源码理解,可以查看本人上一篇博客,本文将不再赘述AQS中的源码分析。
final void lock() {
// 原子操作state变量,当state为0时将其置为1
if (compareAndSetState(0, 1))
// 成功将其置为1表示
// 将当前线程设置为独占状态
setExclusiveOwnerThread(Thread.currentThread());
else
// CAS操作state变量失败,表示出于有锁状态
acquire(1);
}
acquire方法为AQS的方法,但其调用的tryAcquire则由NonfairSync实现

public final void acquire(int arg) {
//当tryAcquire返回false时,执行acquireQueued方法,它是将当前线程加入到队列中等待锁释放
// acquireQueued在AQS中实现,主要逻辑是将当前线程封装成一个对象,加入到同步队列等待锁释放,然后再争用锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

tryAcquire方法,获取锁的方法

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
// 获取当前锁状态
int c = getState();
// 为0表示出于无锁
if (c == 0) {
// 比较并交换锁的值
if (compareAndSetState(0, acquires)) {
// 设置当前线程为独占状态
setExclusiveOwnerThread(current);
return true;
}
}
// 如果当前线程和独占锁的线程是同一个线程,也就是重入
else if (current == getExclusiveOwnerThread()) {
//重入时将锁状态量加1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 出于有锁状态时,直接返回false,表示该线程获取锁失败
return false;
}

1.2 lock方法的公平实现
现在来看公平锁的lock方法

final void lock() {
// 还是调用的祖父类的acquire方法
acquire(1);
}

祖父类的acquire调用的公平锁的tryAcquire实现,细心的人可能一眼就会发现和非公平锁的获取锁实现的唯一区别就在获取锁之前判断之前是否已经线程在等待锁释放

protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors 用来判断是否有其它线程在等待锁释放
// hasQueuedPredecessors 为AQS 实现,就是判断同步队列里是否有其它线程在等待
if (!hasQueuedPredecessors() &&
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;
}****

至此lock方法的公平和非公平实现已完毕。

unlock 方法
unlock方法是二种策略锁共用的,它也是通过调用的AQS的release方法完成锁释放的

public void unlock() {
sync.release(1);
}
其它方法比较简单,这里就不细说了。

总结
ReentrantLock一般的使用场景是synchronized不能满足我们的功能需求时才使用;
一般在使用的时候我们使用非公平锁,公平锁比非公平锁的吞吐量明显要低很多,这是因为公平锁在新的线程过来的时候都要去检查同步队列是否有等待锁的线程。
如果你现在在JAVA这条路上挣扎,也想在IT行业拿高薪,可以参加我们的训练营课程,选择最适合自己的课程学习,技术大牛亲授,7个月后,进入名企拿高薪。我们的课程内容有:Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点。如果你想拿高薪的,想学习的,想就业前景好的,想跟别人竞争能取得优势的,想进阿里面试但担心面试不过的,你都可以来,q群号为:779792048

注:加群要求

1、具有1-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加。

2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加。

3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加。

4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加。

5.阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java JDK 源码