前言:
Lock 本质上是一个接口,它定义了释放锁和获得锁的抽象方法,定义成接口就意味着它定义了锁的一个标准规范,也同时意味着锁的不同实现。实现 Lock 接口的类有很多其中ReentrantLock:表示重入锁,它是唯一一个实现了 Lock 接口的类。重入锁指的是线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联一次计数器增加重入次数。
1 锁的设计:
- 锁对于共享资源的互斥性,要有一种状态来标识锁的状态;
- 没有获取到锁的线程需要有地方进行存放,线程能够被挂起(释放CPU资源)和唤醒;
- 需要体现锁的公平性和非公平性;
- 需要实现锁的重入性;
技术实现: - 可以定义state 来标识锁的状态;
- 可以使用wait/notify|condition ,LockSupport.park(),unpark() 支持挂起和唤醒线程;使用双向链表来存放没有获取到锁的线程;
- 公平和非公平可以使用业务逻辑实现;
- 锁的冲入性,可以判断当前的线程id 来判断是否是同一个线程获取到锁;
2 ReentrantLock 的实现:
2.1 示例代码:
package org.lgx.bluegrass.bluegrasscoree.util.lockUtil;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description TODO
* @Date 2022/10/31 16:57
* @Author lgx
* @Version 1.0
*/
public class MyLock1 implements Runnable {
static Lock lock = new ReentrantLock();
private static int num = 0;
@Override
public void run() {
lock.lock();// 获取锁
try {
Thread.sleep(1);
num ++ ;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
lock.unlock();// 释放锁
}
}
public static void main(String[] args) throws InterruptedException {
MyLock1 lock1 = new MyLock1();
for (int i = 0; i < 1000; i++) {
new Thread(lock1).start();
}
Thread.sleep(2000);
System.out.println(num);
}
}
2.2 Lok.lock() 获取锁详解:
其中 ReentrantLock 对于获取锁有两种实现,公平锁: FairSync ,非公平锁:NonfairSync,通过new ReentrantLock() 传入参数控制:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
// 默认非公平锁
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
// true 时为公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
2.2.1 先来看非公平锁(NonfairSync):
当一个线程首先通过 lock() 方法来获取非公平锁:
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// compareAndSetState:替换state 的值如果此时内存中的state 为0则替换为1
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
// setExclusiveOwnerThread:设置私有线程属性:
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
- 其中: if (compareAndSetState(0, 1)) 通过cas 操纵尝试将标志位 从 0 替换为1 ,如果成功代表获取锁成功;
- 如果获取到锁,setExclusiveOwnerThread(Thread.currentThread());通过set 操作将 将独占锁标识给与当前线程;
线程获取锁成功即可执行后续业务; - 如果线程1已经获取锁并且没有释放,则线程2就会获取锁失败,执行 acquire(1) 将当前线程加入阻塞队列;
acquire(1) :
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire 方法中 首先 使用 tryAcquire 方法再次获取一次锁(如果此时其它线程释放了锁,则当前线程可以获得锁)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();// 获取当前线程
int c = getState();// 获取state
if (c == 0) { // state=0 说明没有线程去抢占锁
if (compareAndSetState(0, acquires)) {// 通过cas 将0 置为1 标识获得锁
setExclusiveOwnerThread(current);// 设置当前线程为独占锁标识
return true;
}
}
else if (current == getExclusiveOwnerThread()) {// 重入锁当前线程已经获取到锁
int nextc = c + acquires;// 将获得锁的次数加1
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);// 设置加锁的次数
return true;
}
return false;
}
如果获取锁失败则通过acquireQueued(addWaiter(Node.EXCLUSIVE), arg))将该线程2同步到阻塞队列中:
addWaiter(Node.EXCLUSIVE), arg) :此方法会将当前线程封装成一个node对象并 放入一个双向链表的队列中
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); // 封装node 节点 此时waitStatus 为初始值0
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;// 将链表的尾节点指向 pred;( 初始时 tail 为null)
if (pred != null) {// 第一次pred 为null 不进入 if
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);// 执行enq 来添加node 到 链表中
return node;
}
首先通过new Node 来获得一个node,Node node = new Node(Thread.currentThread(), mode);此时 volatile int waitStatus; // 默认值 0; 进入 enq(node) 方法:
enq(node):
private Node enq(final Node node) {
for (;;) {// 自旋(死循环)
Node t = tail; //
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
该方法中传入为获取到锁封装线程2的 node 节点 进入for 循环:
// 第一次for 循环:
Node t = tail; // 此时 t 指向链表的尾节点 为null
if (t == null) { // Must initialize 进行链表的初始化
if (compareAndSetHead(new Node())) // 通过构造方法 生成一个空的node,并将head 指向头节点改node 节点
tail = head; // 将链表的为节点指向 刚生成的node 节点
}
第一次for 循环执行完成后,进行了一个双向链表的初始,此时链表结构如下图:
然后进行第二次for 循环:
for (;;) {
Node t = tail; // 此时t(尾节点) 指向node 不为null
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {// 进入else
node.prev = t; // 此时把 封装号 线程2的 node 节点的前置节点 指向 t
if (compareAndSetTail(t, node)) {// 把链表的尾节点指向封装好的线程2 node节点
t.next = node; // 把头节点的下一节点指向 封装好的线程2 node节点
return t; // 返回头节点
}
}
}
进行第二次循环后此时链表结构如下图:
接下来:addWaiter(Node mode) 方法 返回封装好后的 线程2 node 节点;
扩展:此时如果线程3 进入lock.lock() 代码块中,获取锁失败也进入addWaiter方法时:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);// 封装线程3 node 节点
// Try the fast path of enq; backup to full enq on failure
Node pred = tail; // pred 为指向链表的最后一个节点,不为null
if (pred != null) { // 此时进入if
node.prev = pred;// 此时把封装好的线程3 的node 节点的上一节点指向原链表的尾节点(尾插法)
if (compareAndSetTail(pred, node)) {// 通过cas 操作将链表的尾节点指向封装 好的线程3node 节点
pred.next = node; // 此时把原链表中最后一个节点的下一节点指向封装好的线程3 node 节点
return node; // 返回线程3 node 节点
}
}
enq(node);
return node;
此时链表结构如下图:
返回封装好后的线程node 节点 后进入acquireQueued 方法:获取锁,如果失败则挂起当前线程
final boolean acquireQueued(final Node node, int arg) {// node:传入封装好的线程node节点
// arg : 1
boolean failed = true; // 定时是否获取锁失败
try {
boolean interrupted = false;// 定义当前线程是否被打断
for (;;) {
final Node p = node.predecessor();// 获取当前节点前置节点
if (p == head && tryAcquire(arg)) {// 前置节点为初始节点,尝试获取锁
// 获取锁成功
setHead(node); // 获取锁成功设置当前节点为头节点
p.next = null; // help GC 前置节点的next 置空
failed = false;// 获取锁成功
return interrupted;// 返回当前线程是否被中断过 false
// 此种情况出现在线程2 进入被锁的代码后,被封装到双向链表中以后,// 再一次获取锁,并且获取锁成功,就拿掉原有的链表头节点,并且把线程2 设置为链表// 的头节点 并且返回false
}
// 前置节点不为头节点或者 为头节点但是获取锁失败
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
获取链表的当前节点的前置节点:node.predecessor():
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
设置头节点 setHead(Node node):
private void setHead(Node node) {
head = node; // 设置当前节点为头节点
node.thread = null;// 当前节点的 线程置为空
node.prev = null;// 当前节点的前置节点置空
}
setHead 和 p.next = null; 实际上将 原有的头节点 舍弃, 使用gc 进行回收
获取锁失败后应该阻塞当前线程:shouldParkAfterFailedAcquire(p, node):
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// pred: 当前节点的前置节点, node:当前节点
int ws = pred.waitStatus;// 获取前置节点的waitStatus 值,初始化的节点 都为0
if (ws == Node.SIGNAL)// Node.SIGNAL 为 -1 此时0 == -1
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;// 如果前置节点的值为-1 则可以大胆的阻塞当前线程
if (ws > 0) {// 只有当waitStatus 为 CANCELLED 状态值是为 1 其余状态都小于 0
// 此时如果前置节点为取消的状态时
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;// 将node 的前置节点指向其原有前置节点// 的前置节点,相当于将当前节点的前置节点进行了移除
} while (pred.waitStatus > 0);// 在检查新的前置节点是否为 CANCELLED
// 此do while 循环相当于把从后先前把所有状态值为CANCELLED 节点进行了移除
pred.next = node;// 将前置节点的下一个节点指向 当前节点
// 什么时候会遇到ws > 0的case呢?当pred所维护的获取请求被取消时(也就是node的//waitStatus 值为CANCELLED),这时就会循环移除所有被取消的前继节点pred,直到找到// 未被取消的pred。移除所有被取消的前继节点后,直接返回false
} else {// CONDITION = -2 PROPAGATE = -3 和 0
/*
* 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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);// cas 操作将当前节点的前置节点waitStatus置为 -1
}
return false;
}
最开始封装的node 节点waitStatus 都是初始化的值0,由于shouldParkAfterFailedAcquire()将前置节点的waitStatus 修改为-1后返回false,会继续进行循环。
假设node的前继节点pred仍然不是头结点或锁获取失败,则会再次进入shouldParkAfterFailedAcquire() ,上一轮循环中,已经将pred.waitStatus设置为SIGNAL==-1,则这次会进入第一个判断条件,直接返回true,表示应该阻塞;
此时链表的结构可以如下:只有链表最后一个节点的waitStatus 为 0 其余节点都为-1
显然,一旦shouldParkAfterFailedAcquire返回true也就是应该阻塞,就会执行parkAndCheckInterrupt() 阻塞并且检查是否中断,其中parkAndCheckInterrupt()调用了LockSupport.park(),该方法使用Unsafe类将线程挂起,等待后续唤醒(和await()有点类似),唤醒后就会再次循环acquireQueued里的请求逻辑;
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//
return Thread.interrupted();// 返回当前线程是否被中断状态 如果线程在park 期间被 interrupt() 打断则此时会返回true,并将线程是否打断状态复位为false
}
当前线程在执行acquireQueued过程中抛出了异常,导致线程中止前此时failed = true,会执行cancelAcquire方法(),(从代码角度看只有node.predecessor()方法和tryAcquire(arg)方法会主动抛出异常)将线程的状态改为CANCELLED
finally {
if (failed) //
cancelAcquire(node);
}
cancelAcquire:如果当前线程发生异常进入此方法,从当前节点向前遍历,移除取消状态的节点;
private void cancelAcquire(Node node) { // 参数node : 当前节点
// Ignore if node doesn't exist
if (node == null) // 判断当前节点是否为空
return;
node.thread = null;// 将当前节点thread 置为空
// Skip cancelled predecessors
Node pred = node.prev;// 获取当前节点的前置节点
while (pred.waitStatus > 0) // 当前置节点为取消状态
node.prev = pred = pred.prev;// 移除链表中为取消状态的node 节点
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next; // 获取前置节点的下一个节点
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;// 把当前节点的状态置为 取消状态 1
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
// 如果当前CANCELLED状态的节点是尾节点则,将当前链表的尾节点指向当前节点的前置节点
compareAndSetNext(pred, predNext, null);// 设置当前节点的前置节点的下一个节点为null
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
// 如果是中间节点或者是头节点的情况
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 如果是中间节点
Node next = node.next;// 获取当前节点的下一节点
if (next != null && next.waitStatus <= 0)
// 移除当前节点
compareAndSetNext(pred, predNext, next);
} else {
// 如果移除CANCELLED状态的节点后,如果是头节点,则改双向链表穆目前只有一个节点
// 则唤醒改线程让其尝试去抢占锁
unparkSuccessor(node);
}
node.next = node; // help GC 将当前节点的下一节点指向自己
}
}
acquireQueued方法 返回线程是否被打断的标识,如果线程在挂起的过程中被interrupt() 打断过,则会在acquire(int arg) 方法中进入 selfInterrupt(); 方法设置线程被打断的标识:
/**
* Convenience method to interrupt current thread.
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
2.2.2 lock 公平锁获取:
公平锁也使用lock 方法去获取锁:
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
进入tryAcquire(arg) 去尝试获取锁:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();// 获取当前线程
int c = getState();// 当前锁资源 state 数值
if (c == 0) {// 如果为0 则表示当前资源没有被加锁,可以尝试去获取锁
//如果当前阻塞队列为空,或者当前线程位于阻塞队列的第一个节点则可以去尝试获取锁。
// compareAndSetState 将state 从0替换为1 替换成功则标识获取到锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 获取到锁后标识当前线程获取到了锁
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 如果发现当前获取锁的线程,已经获取到了锁则直接 将state 值+1 ,标识锁的重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
hasQueuedPredecessors:
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.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
// 返回true 的条件: 当头结点和尾节点指向非同一个节点(即阻塞队列不为空) 并且 (头结点的下一节点为空 或者 头节点的下一节点对应的现车非当前线程)
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
如果获取锁失败,则同非公平锁相同执行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg));封装当前线程成为一个node 节点,然后再次尝试去获取锁,如果依然获取锁失败则进存入到双向链表结构的阻塞队列,并且挂起当前线程,等待后续被唤醒然后在此获取锁;
2.2.3 tryLock():
public boolean tryLock() {
// 可以看到此时使用非公平锁立即尝试去获取锁
return sync.nonfairTryAcquire(1);
}
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;
}
2.2.4 lock.tryLock(10,TimeUnit.SECONDS):
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
tryAcquire 方法线切尝试获取一次锁;如果获取锁成功则直接返回,如果失败则执行 doAcquireNanos(arg, nanosTimeout),在给定的时间内去尝试获取锁:
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
// 获取当前自旋结束时间,通过当前纳秒数+超时纳秒数
final long deadline = System.nanoTime() + nanosTimeout;
// 封装当前线程为Node节点,并添加的AQS同步队列队尾
// 此处注意Node节点的waitStatus状态为EXCLUSIVE,即表示独占
final Node node = addWaiter(Node.EXCLUSIVE);
// 是否获取到锁标志位
boolean failed = true;
try {
for (;;) {
// 获取当前节点的上一个节点
final Node p = node.predecessor();
// 如果上一个节点为head节点,并且继续尝试获取锁成功,则当前线程获取到锁
// FIFO队列默认存在一个head节点(不存在则第一次会创建),该头结点为空
if (p == head && tryAcquire(arg)) {
// 获取到锁后,将当前节点置为头结点,并清空头节点内容
setHead(node);
// 将头节点的next指向置为空,处理后则p节点挂空,会被GC回收
p.next = null; // help GC
// 设置failed状态为false,说明竞争锁成功
failed = false;
return true;
}
// 如果没有获取到锁,则用过期时间减去当前时间,判断剩余时间是否合法
nanosTimeout = deadline - System.nanoTime();
// 如果剩余时间小于0,时间不合法,说明线程未获取到锁
if (nanosTimeout <= 0L)
return false;
// 剩余时间>0时处理
// shouldParkAfterFailedAcquire 判断当前节点是否可以挂起,
// nanosTimeout > spinForTimeoutThreshold 表示如果线程剩余时间大于1000纳秒,则线程直接挂起nanosTimeout纳秒
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();// 如果线程在挂起期间被打断则 抛出打断异常
}
} finally {
// 如果自旋完成后,已经没有获取到锁,则将当前节点waitStatus置为失效
if (failed)
cancelAcquire(node);// 移除CANCELLED 状态节点
}
}
2.2.4 lockInterruptibly() 可以响应中断的获取锁
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 先尝试去获取一次锁,如果成功则直接返回
// 如果失败则执行 doAcquireInterruptibly,封装node 节点到阻塞队列中,在获取锁失败的时候,挂起现车
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
doAcquireInterruptibly():
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
// 封装node 节点到双向链表中
final Node node = addWaiter(Node.EXCLUSIVE);
// 获取锁成功标识
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();// 获取当前节点的前置节点
if (p == head && tryAcquire(arg)) {// 如果前置节点为首节点,则尝试去获取锁
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
// 没有获取锁则 去挂起线程,如果线程在挂起过程中被interrupt() 打断则抛出中断异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);// 将取消状态的线程从链表中移除
}
}
parkAndCheckInterrupt():
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);// 挂起当前线程
return Thread.interrupted();// 返回现车是否被打断标识
}
2.3 lock.unlock() 释放锁:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {// 尝试去释放锁
// 释放锁成果
Node h = head;
if (h != null && h.waitStatus != 0)// 如果头节点waitStatus 不为0(即阻塞队列存在需要获取锁的线程)
unparkSuccessor(h);// 去唤醒一个线程去抢占锁
return true;
}
return false;
}
tryRelease():
protected final boolean tryRelease(int releases) {
int c = getState() - releases;// 获取锁的重入次数
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();//如果不是当前持有锁的线程去释放锁则抛出异常
boolean free = false;// 释放锁成功标识
if (c == 0) {// 如果当前线程获取锁的重入次数是0
free = true;// 释放成功
setExclusiveOwnerThread(null);// 当前持有锁的线程,属性置空
}
setState(c);// 设置锁的state 值
return free;
}
unparkSuccessor(h):
private void unparkSuccessor(Node 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.
*/
int ws = node.waitStatus;// 头节点的waitStatus 不为0 设置为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 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.
*/
Node s = node.next;// 获取头节点的下一节点
if (s == null || s.waitStatus > 0) {// 下一节点为空,或者下一节点对应的线程已经是取消状态
s = null;
// 从后向前遍历去获取一个可以去抢占锁的线程节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);// 唤醒线程去抢占锁
}
3 总结:
3.1 lock 获取锁是否公平,只体现在获取锁的时候是否允许线程插队,如果一个线程在去获取锁的时候,立即去尝试获取锁,而不管是否已经存在因为获取不到锁而阻塞的队列,则为非公平锁;在释放锁的时候,都是从阻塞队列中唤醒一个等待时间最长的线程去抢占锁;
3.2 lock 锁中使用了大量的cas 比较和交换 操作来避免线程的直接阻塞;
4 扩展:
4.1 线程中断:Thread interrupt() ;Thread.currentThread().isInterrupted();Thread.interrupted();
- Thread.currentThread().interrupt():表示为当前线程打上中断的标记,并且唤醒阻塞中的线程,继续向下执行;
- Thread.interrupted():表示清除中断标记,如果当前线程中断,返回true,否则返回false;
- Thread.currentThread().isInterrupted():表示查看当前线程的状态是否中断,不清除中断标记,如果当前线程中断,返回true,否则返回false;
示例代码,可以看出当线程因为获取锁失败而被阻塞时,调用 interrupt() 方法后 会唤醒park 的线程,并在线程t2 获取到锁后,得到其中断的标识为true,
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description TODO
* @Date 2022/10/28 14:43
* @Author lgx
* @Version 1.0
*/
public class ReentrantLockTest {
static Lock lock = new ReentrantLock(true);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
lockTestA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
Thread t2 = new Thread(() -> {
try {
lockTestB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2");
t1.start();
t2.start();
t2.interrupt();
}
private static void lockTestA() throws InterruptedException {
lock.lock();
try {
Thread.sleep(100000);
Thread thread = Thread.currentThread();
System.out.println("thread.isInterrupted() = " + thread.getName() + "," + thread.isInterrupted());
if (thread.isInterrupted()) {
System.out.println("thread.getName(): 中断过 = " + thread.getName());
}
} catch (InterruptedException ex) {
} finally {
lock.unlock();
}
}
private static void lockTestB() throws InterruptedException {
lock.lock();
try {
Thread thread = Thread.currentThread();
System.out.println("thread.isInterrupted() = " + thread.getName() + "," + thread.isInterrupted());
if (thread.isInterrupted()) {
System.out.println("thread.getName(): 中断过 = " + thread.getName());
}
} catch (Exception ex) {
System.out.println("ex.getMessage() = " + ex.getMessage());
} finally {
lock.unlock();
}
}
}
4. 2 CAS 比较和更新:
Compare And Swap 比较和交换,是用于实现多线程同步的原子指令 。将给定的值与内存位置上的内容进行比较,只有在相同的情况下,才将给的新值写入到内存位置上;
CAS的使用:
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B;
示例:
- 在内存地址V当中,存储着值为10的变量
- 此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11
- 在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11
- 线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败
- 线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋
- 线程1进行SWAP,把地址V的值替换为B,也就是12
- 线程1进行SWAP,把地址V的值替换为B,也就是12
CAS 优缺点:
优点:
在并发量不是很高时cas机制会提高效率;
缺点: - .CPU开销较大,在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
- 不能保证代码块的原子性,CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
-ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。