说明
- 本文中采用的 jdk 版本为 openjdk-1.8
锁和线程状修改
- 加锁的本质?主要是为了在访问临界资源的时候,能够实现一个等待唤醒得有序操作。
- Java 中的锁的分类:
sychronized
和Lock
。在sychronized
中主要是通过monitor
来进行实现的。通过Object.wart/notify
实现线程得阻塞和唤醒; 第二种就是基于线程的LockSupport.park/unpack
阻塞和唤醒。
- 线程的中断问题,如何优雅的中断一个线程?
- Java中断机制是一种协作机制,也就是说中断并不能直接终止某一个线程,而需要被中断的线程自己处理中断
- API 的使用:
interrupt(): 将线程的中断标示位设置为 true;
isInterrupted(): 判断当前线程的中断标志位是否是 true;
Thread.interrupted(): 判断当前线程中断位置是否位 true, 并且清除中断标志位,重置为 fasle。
- LockSupport 会造成线程中断吗? LockSupport 不会造成线程中断的。
- CAS 是什么?比较交换, 主要是一个乐观锁的概念, 底层采用的是 unsafe api 来实现比较交换。
protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
AQS 的原理和实现
AQS 是基于队列在实现排队 AbstractQueuedSynchronizer
类为模板方法的实现。
- 底层数据结构是一个双向链表。每个 Node 节点包含 prev,和 next 指针,以及数据数据字段,这里的数据字段用就是一个线程 Thread 对象。
- Node 的四种状态: 取消,等待,条件等待,共享状态
- 通常的两种实现:公平锁,非公平锁。 怎么提现公平指的是同一个时刻新加入的数据,和队列头的数据竞争是否能够进行公平的资源竞争。
- 状态的修改通过 CAS 来实现,底层是调用 sun.misc.Unsafe 的
compareAndSwapInt
进行状态的修改。
- 在线程进入队列之前会进行尝试加锁,如果拿不到锁会阻塞当前线程并且线程通过
LockSupport.park()
进入阻塞。
- 释放锁的时候,就会去队列中拿队列头的节点,进行唤醒,
同步队列的头节点 head 就是当前获取锁的线程
。
- 下面是获取锁中 AQS 的核心代码
a. 尝试加锁,如果加锁不成功,就进入队列进行重试。
// 独占的方式获取锁, 可以忽略中断, 最少调用一次,如果失败会进行排队,直到成功 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
b. 队列中,先去判断是否是队列头,如果是去尝试加锁,如果不是就对 Node 的状态进行修改,修改为等待唤醒。状态修改成功后把线程状态修改为阻塞。
final boolean acquireQueued(final Node node, int arg) { 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 failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
下面是 shouldParkAfterFailedAcquire 方法对 Node 的状态进行维护。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; // 前继节点的状态, 第一次进入默认值 0 if (ws == Node.SIGNAL) return true; if (ws > 0) { do { // 出队 node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 第一次进来, pred.waitStatus = 0 执行这个分支 // 将前继节点的状态修改为 SIGNAL, 表示 pred.next 节点需要被唤醒(此时准备进入阻塞, 但是还未被阻塞, 再次获取锁失败之后才会被阻塞) compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
AQS 流程图
ASQ 特征
- 阻塞等待队列
- 共享独占
- 公平/非公平
- 可重入
- 允许中断