如果你相信你做什么都能成,你会自信的多!
千万不要总自我否定,尤其是职场的打工人。如果你经常感觉,这个做不好,那个学不会,别的也不懂,那么久而久之会越来越缺乏自信。
一般说能成事的人都具有
赌徒
精神,在他们眼里只要做这事那就一定能成,当然也有可能最后就没成,但在整个过程中人的心态是良好的,每天都有一个饱满的精神状态,孜孜不倦的奋斗着。最后也就是这样的斗志让走在一个起点的小伙伴,有了差距。
谢飞机,小记
,今天打工人呀,明天早上困呀,嘟嘟嘟,喂?谁呀,打农药呢!?
「谢飞机」:哎呦,面试官大哥,咋了!
「面试官」:偷偷告诉你哈,你一面过了。
「谢飞机」:嘿嘿,真的呀!太好了!哈哈哈,那我还准备点什么呢!?
「面试官」:二面会比较难喽,嗯,我顺便问你一个哈。AQS 你了解吗,ReentrantLock 获取锁的过程是什么样的?什么是 CAS?...
「谢飞机」:我我我,脑子还在后羿射箭里,我一会就看看!!
「面试官」:好好准备下吧,打工人,打工魂!
ReentrantLock 可重入独占锁涉及的知识点较多,为了更好的学习这些知识,在上一章节先分析源码和学习实现了公平锁的几种方案。包括:CLH、MCS、Ticket,通过这部分内容的学习,再来理解 ReentrantLock 中关于 CLH 的变体实现和相应的应用就比较容易了。
接下来沿着 ReentrantLock 的知识链,继续分析 AQS 独占锁的相关知识点,如图 17-1
图 17-1 ReentrantLock 的知识链
在这部分知识学习中,会主要围绕 ReentrantLock 中关于 AQS 的使用进行展开,逐步分析源码了解原理。
AQS 是 AbstractQueuedSynchronizer 的缩写,几乎所有 Lock 都是基于 AQS 来实现了,其底层大量使用 CAS 提供乐观锁服务,在冲突时采用自旋方式进行重试,以此实现轻量级和高效的获取锁。
另外 AbstractQueuedSynchronizer 是一个抽象类,但并没有定义相应的抽象方法,而是提供了可以被字类继承时覆盖的 protected 的方法,这样就可以非常方便的支持继承类的使用。
在学习 ReentrantLock 中应用的 AQS 之前,先实现一个简单的同步类,来体会下 AQS 的作用。
public class SyncLock {
private final Sync sync;
public SyncLock() {
sync = new Sync();
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1);
}
@Override
protected boolean tryRelease(int arg) {
setState(0);
return true;
}
// 该线程是否正在独占资源,只有用到 Condition 才需要去实现
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
}
这个实现的过程属于 ReentrantLock 简版,主要包括如下内容:
@Test
public void test_SyncLock() throws InterruptedException {
final SyncLock lock = new SyncLock();
for (int i = 0; i < 10; i++) {
Thread.sleep(200);
new Thread(new TestLock(lock), String.valueOf(i)).start();
}
Thread.sleep(100000);
}
static class TestLock implements Runnable {
private SyncLock lock;
public TestLock(SyncLock lock) throws InterruptedException {
this.lock = lock;
}
@Override
public void run() {
try {
lock.lock();
Thread.sleep(1000);
System.out.println(String.format("Thread %s Completed", Thread.currentThread().getName()));
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
「测试结果」
Thread 0 Completed
Thread 1 Completed
Thread 2 Completed
Thread 3 Completed
Thread 4 Completed
Thread 5 Completed
Thread 6 Completed
Thread 7 Completed
Thread 8 Completed
Thread 9 Completed
CAS 是 compareAndSet 的缩写,它的应用场景就是对一个变量进行值变更,在变更时会传入两个参数:一个是预期值、另外一个是更新值。如果被更新的变量预期值与传入值一致,则可以变更。
CAS 的具体操作使用到了unsafe类,底层用到了本地方法unsafe.compareAndSwapInt比较交换方法。
CAS 是一种无锁算法,这种操作是 CPU 指令集操作,只有一步原子操作,速度非常快。而且 CAS 避免了请求操作系统来裁定锁问题,直接由 CPU 搞定,但也不是没有开销,比如 Cache Miss,感兴趣的小伙伴可以自行了解 CPU 硬件相关知识。
图 17-2 获取锁流程图
图 17-2 就是整个 ReentrantLock 中获取锁的核心流程,包括非公平锁和公平锁的一些交叉流程。接下来我们就以此按照此流程来讲解相应的源码部分。
图 17-3 lock -> CAS
ReentrantLock 实现了非公平锁和公平锁,所以在调用lock.lock();时,会有不同的实现类:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
在非公平锁的实现类里,获取锁的过程,有这样一段 CAS 操作的代码。compareAndSetState赋值成功则获取锁。那么 CAS 这里面做了什么操作?
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
往下翻我们看到这样一段代码,这里是 unsafe 功能类的使用,两个参数到这里变成四个参数。多了 this、stateOffset。this 是对象本身,那么 stateOffset 是什么?
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
再往下看我们找到,stateOffset 是偏移量值,偏移量是一个固定的值。接下来我们就看看这个值到底是多少!
「引用POM jol-cli」
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-cli -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-cli</artifactId>
<version>0.14</version>
</dependency>
「单元测试」
@Test
public void test_stateOffset() throws Exception {
Unsafe unsafe = getUnsafeInstance();
long state = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
System.out.println(state);
}
// 16
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
整个这块代码里面包含了四个方法的调用,如下:
Thread.currentThread().interrupt()方法调用,它的主要作用是在执行完 acquire 之前自己执行中断操作。
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;
}
这部分获取锁的逻辑比较简单,主要包括两部分:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 如果队列不为空, 使用 CAS 方式将当前节点设为尾节点
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 队列为空、CAS失败,将节点插入队列
enq(node);
return node;
}
「enq」
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;
}
}
}
}
for循环 + CAS 入队列。
「注意,从尾节点逆向遍历」
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 当前节点的前驱就是head节点时, 再次尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 获取锁失败后, 判断是否把当前线程挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
当获取锁流程走到这,说明节点已经加入队列完成。看源码中接下来就是让该方法再次尝试获取锁,如果获取锁失败会判断是否把线程挂起。
「setHead」
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
在学习 CLH 公平锁数据结构中讲到Head节点是一个虚节点,如果当前节点的前驱节点是Head节点,那么说明此时Node节点排在队列最前面,可以尝试获取锁。
获取锁后设置Head节点,这个过程就是一个出队列过程,原来节点设置Null方便GC。
「shouldParkAfterFailedAcquire」
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// SIGNAL 设置了前一个节点完结唤醒,安心干别的去了,这里是睡。
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
你是否还CANCELLED、SIGNAL、CONDITION 、PROPAGATE ,这四种状态,在这个方法中用到了两种如下:
「那么」,以上这段代码主要的执行内容包括:
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
// 线程挂起等待被唤醒
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://mp.weixin.qq.com/s/LL9aBCCL-ZhpbZMl_GlgSQ
内容来源于网络,如有侵权,请联系作者删除!