文章内容引用自 咕泡科技 咕泡出品,必属精品
文章目录
- 1ReentrantLock解释
- 1.1可重入栗子:
- 1.2看到了栗子,让我们带着问题一起去探究它的原理吧
- 2看答案之前想一下,如果让我们自己实现一把锁,你会怎么做?
- 3ReentrantLock 源码解析
- 3.01一张对源码解释的大图,思路清晰
- 3.1构造函数
- 3.2lock方法
- 3.2.1compareAndsetstate
- 3.2.2acquire
- 3.2.3所谓的重入nonfairTryAcquire
- 3.2.4等待队列acquireQueued前的准备
- 3.2.4.1形成一个双向链表addWaiter
- 3.2.4.2自旋
- 3.2.4.3如果再来一个线程抢锁
- 3.2.5执行acquireQueued方法
- 3.2.5.1shouldParkAfterFailedAcquire方法
- 3.2.5.2parkAndCheckInterrupt方法
- 3.3unlock解锁
- 3.3.1tryRelease 方法
- 3.3.2unparkSuccessor方法
- 3.3.3为什么要从后往前?
- 3.4唤醒head的下一个节点
- 公平锁与非公平锁区别
- 什么是AQS?
- Lock与Synchronized的区别
1ReentrantLock解释
我们知道在并发的场景下,如果同时对共享代码块进行访问时,会导致原子性、有序性、可见性问题。从而导致我业务出错。所以有时候,我们有些代码块不能进行并行,就得去改成串行!!之前我们已经知道了一个Synchronized重量级锁。该锁底层是JVM里面,通过monitor锁来实现串行,其他线程进行等待。
那么我们今天要讲的,也是一个锁,只不过相关实现是在java层面去做的。就是ReentrantLock
可重入锁。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。
1.1可重入栗子:
代码语言:javascript复制public class ReentrantLockTest {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
for (int i = 1; i <= 3; i ) {
lock.lock();
}
for(int i=1;i<=3;i ){
try {
} finally {
lock.unlock();
}
}
}
}
上面的代码通过lock()方法先获取锁三次,然后通过unlock()方法释放锁3次,程序可以正常退出。从上面的例子可以看出,ReentrantLock是可以重入的锁,当一个线程获取锁时,还可以接着重复获取多次。在加上ReentrantLock的的独占性,我们可以得出以下ReentrantLock和synchronized的相同点。
1.ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。但是实现上两者不同:synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。
2.ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。
上面的栗子过于简单,不够香甜,再来一个:
代码语言:javascript复制public class ReentrantLockTest {
int i=0;
ReentrantLock reentrantLock=new ReentrantLock();
public void incr() {
reentrantLock.lock(); //加上ReentrantLock
i =10;
System.out.println(i);
reentrantLock.unlock(); //撤掉ReentrantLock,想一下为啥要在代码层面撤掉
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockTest test=new ReentrantLockTest();
Thread[] threads=new Thread[5];
for (int j = 0; j < 5; j ) {
threads[j] =new Thread(() -> {
test.incr();
});
threads[j].start();
}
for (int j = 0; j < 5; j ) {
threads[j].join();
}
}
}
在每个线程执行incr方法时,都会先去reentrantLock执行lock方法拿锁。 那么执行结果就是线程安全的!
可是这样,每次只能有1个线程能执行incr方法,其他线程都是等待的!! 如果等待的线程太多,那么也会有问题,所以也可以不让线程等待,失败就失败,比如以下代码多线程执行,那么只会有1个执行成功,其他的线程拿不到锁
代码语言:javascript复制ReentrantLock reentrantLock=new ReentrantLock();
public void incr() {
if (reentrantLock.tryLock()) {
i = 10;
System.out.println(i);
reentrantLock.unlock();
}
}
1.2看到了栗子,让我们带着问题一起去探究它的原理吧
ReentrantLock 相关面试问题: 【1】什么是可重入,什么是可重入锁? 它用来解决什么问题? 【2】ReentrantLock的核心是 AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。 【3】ReentrantLock 是如何实现公平锁的? 【4】ReentrantLock 是如何实现非公平锁的? 【5】ReentrantLock 默认实现的是公平还是非公平锁? 【6】使用ReentrantLock 实现公平和非公平锁的示例? 【7】ReentrantLock 和 Synchronized的对比?
为什么叫可重入?核心是什么,原理是怎么样的? 在我们直接看答案之前,不妨让我们想一下:
2看答案之前想一下,如果让我们自己实现一把锁,你会怎么做?
需求很简单,我的代码块只能同时有1个线程来执行,当存在并发时,也就是如果有多个线程来抢占的话,去排队或者处理失败。
第一,只能有1个线程来执行,那么我们就得有个标记,来标记这个任务是否有线程在执行,并且这个标记是不能有并发的。 比如我们之前的synchronized锁,那么这个标记就是在对象头,如果是不同的对象,那么就会失效。 那这个标记有什么要求呢?
- 这个标记是不是需要线程可见
- 获取这个标记必须要线程安全的,如果多个线程都能并行的得到标记,那也就失去了标记的意义。
还有我会记录当前之前这个代码的线程是哪个。 拿不到锁的线程呢?要么等待(用一些数据结构进行保存)要么就处理失败,所以:
第二.能抢到的线程的正常执行代码块,如果抢不到,那么要么排队,要么就不管。 就好比你在动车上。厕所就一个,但是可能有很多人肚子痛,那么肯定是第一个抢到的人用厕所,其他的人排队。或者不上了
一切锁的实现思想大概都是这个,本质上都是一样的,只是实现方式不一样
3ReentrantLock 源码解析
3.01一张对源码解释的大图,思路清晰
有同学反馈看着比较困难。。。 emmmmm 连夜画了一张图,标出了代码的大致逻辑,下面的源码看不太懂的可以参照着这张图看
这张图我画了一下午,给个赞点点关注吧 ReentrantLock 的使用,无非就是lock和unlock和别的代码配合着使用,从lock开始看,看之前,按照我看源码的习惯,我会先去看一下构造函数,看一下成员变量,方便我们去理解和查阅源码
3.1构造函数
代码语言:javascript复制public ReentrantLock() {
sync = new NonfairSync();
}
好嘛,构造就一行,new了一个非公平锁,往下翻找lock函数,我们发现Lock会有2个类实现,一个是FairSync 一个是NonfairSync。这2个的区别我们后面会讲,其实就是一个是公平锁,一个是非公平锁。
在创建ReentrantLock对象的时候,默认是非公平锁,所以,我们先看非公平锁逻辑
3.2lock方法
代码语言:javascript复制final void lock( {
//cas 比较并替换,修改AbstractQueuedsynchronizer类的state字段,用vo1atile修饰,并且jvm底层cas会有锁操作,只能有1个线程能更改成功
if (compareAndsetstate(0,1))//如果从0改成1 说明能抢占到锁
//修改Abstractownab1esynchronizer的exclusiveownerThread为当前线程
setExclusiveownerThread(Thread. currentThread());
else
//线程抢占不到的情况执行
acquire(1);
}
3.2.1compareAndsetstate
里面的compareAndsetstate就是我们经常见也经常听说的CAS了 总结一下,该方法,如果能抢占到锁,做了2件事情
- 将AbstractQueuedSynchronizer类的state字段从0改成1 ,
- AbstractOwnableSynchronizer的exclusiveOwnerThread为当前线程 exclusiveOwnerThread是AbstractOwnableSynchronizer类里面的一个字段
state字段作为是否拿到锁的标记
如果拿不到呢,第一个线程拿到锁了但是还没有释放,这时另一个线程过来说我也想要这个锁:
代码语言:javascript复制 final void lock() {
//因为被线程1已经更改为1了, 所以执行失败
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//进入acquire
acquire(1);
}
3.2.2acquire
看看acquire
代码语言:javascript复制 public final void acquire(int arg) {
//尝试去加锁,看锁是不是释放了,该方法也有2个实现,FairSync与 NonfairSync,我们暂时只看默认的NonfairSync
//如果tryAcquire为false,则执行acquireQueued,并且 acquireQueued为true,执行selfInterrupt
if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
看一下作为判断条件的tryAcquire方法:
代码语言:javascript复制 protected boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
呵,往里点吧 代码逻辑都写在注释里面了:所谓的重入 就在这了
3.2.3所谓的重入nonfairTryAcquire
代码语言:javascript复制 final boolean nonfairTryAcquire(int acquires) {
//获取当前线程,现在我们线程2来抢占锁,并且线程1没有执行完,所以 当前是线程2
final Thread current = Thread.currentThread();
//获取AbstractQueuedSynchronizer的值,因为线程1把他设为了 1,所以c为1
int c = getState();
//c为0,是线程1释放了锁的场景,但是现在没有,不满足
if (c == 0) {
//如果满足,尝试着去抢占锁(把state改成1)
if (compareAndSetState(0, acquires)) {
//设置当前执行代码块的线程为线程2
setExclusiveOwnerThread(current);
//返回ture,代表线程2拿到了锁,去正常执行业务代码
return true;
}
}
//如果c!=0.说明有线程在占有锁,那么如果占有的线程跟当前的线程一 致,说明是同一个线程抢占多次锁
else if (current == getExclusiveOwnerThread()) {
//将state 改成state 1 这个时候,state不止是一个状态,而 是代表加锁了多少次(重入次数)
int nextc = c acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果抢占不到锁,并且当前线程不是占有锁线程,返回false
return false;
}
所以,返回了个false,执行acquireQueued方法:
3.2.4等待队列acquireQueued前的准备
执行acquireQueued,里面先执行addWaiter方法,该方法的主要作用是去形成一个双向链表
3.2.4.1形成一个双向链表addWaiter
代码语言:javascript复制//参数mode 为null(在lock没有用处 condition中有用)
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
//new node对象,对象为AQS中的一个数据结构,里面存有线程信息,以及前 后节点数据、首尾节点等信息
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;//tail node里面定义尾结点,默认为null
//所以,thread2进来的时候,pred为null,不满足下面条件
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//进入enq方法
enq(node);
//返回node节点
return node;
}
瞅一眼enq方法
3.2.4.2自旋
代码语言:javascript复制//传入的是new的那个node
private Node enq(final Node node) {
//自旋
for (;;) {
//tail默认为null
Node t = tail;
//第一次满足条件
if (t == null) { // Must initialize
//cas去设置head节点
if (compareAndSetHead(new Node()))
//设置成功,将tail设置为head节点,继续自旋
tail = head;
} else {
//第二次自旋,当t!=null时进入
//node的前节点指向t
node.prev = t;
//cas将tail节点设置为node
if (compareAndSetTail(t, node)) {
//设置t的next为node
t.next = node;
//跳出循环,返回t
return t;
}
}
}
}
现在,第二个线程抢不到锁的结果已经明示了,建了一个等待队列:
3.2.4.3如果再来一个线程抢锁
假如thread3也来抢占锁,这个时候锁还没释放。还是会进入addWaiter方法:
代码语言:javascript复制 private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
//new一个thread3的node
// Try the fast path of enq; backup to full enq on failure
//tail现在指向的是thread2,所以pred为thread2的node
Node pred = tail;
//不为null,满足条件
if (pred != null) {
//thread3的node指向thread2的node
node.prev = pred;
//将tail节点的指向改成thread3的node
if (compareAndSetTail(pred, node)) {
//thread2的next指向thread3的节点
pred.next = node;
//返回thread3的node
return node;
}
}
enq(node);
return node;
}
经常刷算法的看到这里肯定感到异常的熟悉,这不就是往双向链表后面插节点嘛
thread3抢占不到锁的时候,我们的node列表变成了如下图
那么有多少线程抢占锁,我的双向列表就会有多少个等待节点。 现在,什么是重入,以及等待的列表、ReentrantLock内部结构、基于哪种锁实现,是不是随着代码的跟进一步一步都清楚了?
终于正式执行acquireQueued方法了:
3.2.5执行acquireQueued方法
该方法主要是根据node节点去操作相关获取锁以及park操作
代码语言:javascript复制//node为当前线程的node节点
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取node节点的前一个节点
final Node p = node.predecessor();
//如果p==head 如果是thread2,那么满足,但是thread2 tryAcquire失败,因为thread1占有锁;所以不满足
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//进入shouldParkAfterFailedAcquire与 parkAndCheckInterrupt
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.2.5.1shouldParkAfterFailedAcquire方法
瞅一眼shouldParkAfterFailedAcquire方法:
线程状态大家还不知道吧,这里贴出来
线程的状态标识
变量 waitStatus 则表示当前 Node 结点的等待状态,共有5种取值 CANCELLED
、SIGNAL
、CONDITION
、PROPAGATE
、0
。
- CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
- SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。
- CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从条件队列转移到同步队列中,等待获取同步锁。
- PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
- 0:新结点入队时的默认状态。
注意,负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常。
这个方法的官方注释一大堆,我再解释一下
代码语言:javascript复制//pred为当前线程的前节点 node为当前线程节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//得到节点的状态,默认为0 第二 次进入为-1
if (ws == Node.SIGNAL)//waitStatus!=-1不满足 第二次 满足条件,返回true
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {//不满足
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
//该逻辑是当我前一个节点的线程状态取消时,将前后链表的关系取消
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {//进入else
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//修改当前节点的前一个节点的状态为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//返回false,进入外面的自旋,
return false;
}
重新进入自旋,进入shouldParkAfterFailedAcquire方法,判断ws=-1,满足条件 走到这里,流程图如下:
这时如果thread3进来尝试获取锁
瞅一眼parkAndCheckInterrupt方法:
3.2.5.2parkAndCheckInterrupt方法
代码语言:javascript复制private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); //如果没有拿到锁,线程park waiting状态 唤醒有2个场景(1第一个线程释放锁唤醒 2interrupt优雅中断)
return Thread.interrupted(); //获取中断状态并且复位
}
有不知道interrupt优雅中断什么意思的小伙伴可以看一下我的另一篇博客: 链接: 怎么查看线程的状态及interrupt优雅的关闭线程和interrupt()、interrupted()、isInterrupted()的作用以及区别在哪?
至此,Lock的代码就走完了,线程一拿着锁,线程二进来,建了等待队列的头,把自己放在末尾然后调用park阻塞自己,等待线程一释放锁,线程三如果进来,将自己放在等待队列后面阻塞自己········
现在,假如thread1业务代码已经执行完,调用unlock
3.3unlock解锁
代码语言:javascript复制public void unlock() {
sync.release(1);
}
点进release:
代码语言:javascript复制 public final boolean release(int arg) {
//调用tryRelease方法 尝试释放锁
if (tryRelease(arg)) {
//如果锁释放成功
Node h = head;
//得到head节点,如果head节点!=null 并且状态!=0 满足条件
if (h != null && h.waitStatus != 0)
//执行unparkSuccessor
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease 方法 去更改state状态
3.3.1tryRelease 方法
代码语言:javascript复制 protected final boolean tryRelease(int releases) {
//假如thread1只重入了一次。c-1=0
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
//判断加锁的线程是不是当前需要释放的线程
throw new IllegalMonitorStateException();
boolean free = false;
//如果thread1只加锁一次
if (c == 0) {
free = true;
//将当前拿锁线程设置为空
setExclusiveOwnerThread(null);
}
//将state设置为0
setState(c);
return free;
}
tryRelease 执行完毕后,state=0 ,exclusiveOwnerThread=null。这个时候,其他线程可以抢占锁了。 执行unparkSuccessor:
3.3.2unparkSuccessor方法
代码语言:javascript复制//node 为head节点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
//得到head的节点状态
int ws = node.waitStatus;
if (ws < 0)//现在的状态为-1 满足条件
compareAndSetWaitStatus(node, ws, 0);//将头节点改成 0
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//得到head节点的下一个节点,我们的场景为thread2的
Node s = node.next;
//thread2node的状态为-1,正常状态
if (s == null || s.waitStatus > 0) {
s = null;
//如果需要释放锁的那个线程是取消状态,从后往前找到最前面的那 一个node
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//满足条件
if (s != null)
//唤醒thread2线程
LockSupport.unpark(s.thread);
}
如果获取锁的线程node被取消或者异常,那么从后往前找到异常节点后的第一个正常node
3.3.3为什么要从后往前?
因为有可能出现前指针没有的场景,在enq存在并发的时候(就是前面那个enq):
代码语言:javascript复制 private Node enq(final Node node) {
//自旋
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//node的前节点指向t
node.prev = t;
if (compareAndSetTail(t, node)) {
//以下逻辑再其他现在执行完之后执行就会出现如下图场 景,所以必须从后往前
t.next = node;
return t;
}
}
}
}
3.4唤醒head的下一个节点
记得刚才3.2.5.2的parkAndCheckInterrupt方法嘛 进入到阻塞的地方parkAndCheckInterrupt方法
代码语言:javascript复制private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); //被释放锁唤醒,如果外面中断过范围false,否则返回true,并复位
return Thread.interrupted();
}
进入acquireQueued方法的自旋
代码语言:javascript复制 final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取thread2 node的前节点
final Node p = node.predecessor();
//thread2的前节点是head,并且这个时候thread1已经释放 锁,能拿到锁 if
if (p == head && tryAcquire(arg)) {
//将thread2的节点设置为首节点,并且把thread2节点 的thread、pre设置为null
setHead(node);
//这个时候之前的node节点还存在强引用,将next关联 去除,方便回收之前的head节点
p.next = null; // help GC
failed = false;
//返回
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
走完后得到图如下:
至此,加锁与释放锁都看完了,前面提出的问题你是否得到了答案呢? 学过算法的小伙伴应该看起来不吃力,因为这里面的代码其实就是我们刷算法题的解题思路嘛,这也是大厂为什么要求一定要考察算法的一个原因吧,思想真的很重要 没刷过算法的同学们,你们看起来不吃力的话,那说明有天赋啊,赶紧刷刷算法题,冲大厂!!
公平锁与非公平锁区别
我们Lock有2个类的实现,一个是FairSync,一个是NonfairSync。FairSync是公平锁 NonfairSync是非公平锁。
公平,体现在我所有的线程,都必须按照排队的顺序来,不能进行插队。
什么时候可以插队? 当我释放锁的时候,会先去更改state以及占有锁的线程。这个时候所有的线程都能去抢占锁,如果有不是在队列里的线程来抢占锁,也是能抢到锁的。
所以,如果我限制,当我释放锁的时候,必须是等待队里的线程才能获取锁则是公平锁,如果没有限制,则是非公平锁。
限制代码,在公平锁获取锁的时候,判断源码如下 : FairSync尝试加锁的代码
代码语言:javascript复制protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//获取锁加了条件hasQueuedPredecessors
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;
}
hasQueuedPredecessors方法: 如果返回true,不允许抢占 false是允许抢占
代码语言:javascript复制public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
//先获取tail,防止tail不为空 head为空的情况 但是head为空, tail为空或者都为空没事
//保证不会h为空还进入h.next判断
Node t = tail; // Read fields in reverse initialization order
Node h = head; Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
- 如果h!=t 说明 有线程在等待 或者正在抢占(head不为null。tail为null 的情况) 这个时候执行
((s = h.next) == null || s.thread != Thread.currentThread());
反之,没有线程在等待,为false,允许抢占锁
- 假如有线程在等待,则 ((s = h.next) == null || s.thread != Thread.currentThread())满足一个条件就不允许抢占锁 都为false就允许抢 占
(s = h.next) == null head的下一个节点为空 代表有线程进入enq队列,但 是还没执行完,这个时候,其他线程不可抢占
s.thread != Thread.currentThread() 代表头节点的下一个节点不是我当前 抢占锁的线程!不可抢占
总之。该方法就是保证,我抢占到锁的线程,如果等待队列有数据或者正在进数据,那必须只能是head的下一个节点得到锁
什么是AQS?
AQS是juc包下的一个工具类,全称是AbstractQueuedSynchronizer 抽象队列同步器。我们并发编程中的很多类的功能底层都是基于AQS来实现。比如ReentrantLock 、CountDownLatch 、condition等等。
首先数据结构。既然是实现并发的核心类,那么AQS中维护了一个state字段,代表锁的抢占情况。并提供对state的cas操作。以及提供加锁的模板方法,比如tryAcquire,自己可以去重现实现相关逻辑。
同时,抢不到的线程需要排队并且等待,所以AQS中有个线程等待队列。它里面最主要的是有一个双向列表。 节点的数据结构是node node存有线程的信息,以及node的状态。同时提供对双向列表的相关操作方法。
如果线程抢占不到锁,就会进入AQS中的等待队列,并且park。
同时提供了释放锁都相关方法,释放锁会唤醒相关线程。进行锁抢占操作。
Lock与Synchronized的区别
1.synchronized底层jvm层面实现 ReentrantLock 为java的juc类 2.synchronized异常会释放锁 lock必须手动释放 3.synchronized不能响应中断,lock可以响应中断 4.synchronized可重入,非公平 ; lock可重入,可公平与非公平 5.Lock提高读写能力,在1.6之前性能比synchronized高,但是1.6之后差不多了