多线程第三章-AQS及Lock锁
1, LockSupport类
该类是rt.jar包下的一个工具类,主要作用是挂起和唤醒线程,是创建锁和其他同步类的基础
1.1 主要的方法
- void park() 如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起
@Slf4j
public class LockSupportTest {
public static void main(String[] args) {
log.info("begin lockSupport test");
LockSupport.park();
log.info("end lockSupport test");
}
}
输出结果: begin lockSupport test
- void unPark(Thread thread)
前言
除了synchronized加锁之外,还有lock锁的方式,这俩种锁有什么区别尼?
synchronized
synchronized锁是非公平的锁,是独占的锁,属于抢占式的锁,而且根据synchronized在类中修饰的位置不同,锁的定义也不一样
lock
可以是公平锁也可以是非公平锁,默认实现是非公平的锁,他的实现方式与synchronized也不同,lock是一种可中断锁,使用lockSupport来做线程的中断,
lock实现方式
猜想:
- 锁的互斥性,当一个线程在使用时,其他线程是无法使用的,需要一个status 来记录当前线程是否拥有锁,0(无锁), 1(有锁),
- 没有抢占到锁的线程?-> 释放cpu资源,等待--->唤醒
- 等待的线程如何存储?->使用那种数据结构来处理(能否插队)
- 公平和非公平
- 重入的特性(识别是否是同一个线程)
实现技术方案
- volatile state (0无锁 ,1有锁,-1代表重入)
- wait/notify |condition 唤醒线程? LockSupport.park /unpark
- 双向链表存储等待线程
- 逻辑层面实现公平与非公平
- 早某一个地方存储当前获得锁线程的ID,判断下次抢占锁的线程是否是同一个。
lock的实现源码分析
public interface Lock {
void lock();
}
lock的子类ReentrantLock类的构造函数,根据传值对于公平和非公平锁实现做了定义
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) {
sync = fair ? new FairSync() : new NonfairSync();
}
NonfairSync(非公平锁)
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
/**
* 比较并设置值 CAS,这里调用的是AQS中的unsafe.compareAndSwapInt(this, stateOffset, expect, update);
* unsafe是个native调用的是机器内存值,这里的核心意思是,取用stateOffset当前的值,传入的值是0和1,拿
* 0和stateOffset的值做比较,期望的值是0,然后将stateOffset的值更新成1,这里主要java底层借助计算机内存地址
* 来做的,如何比较的期望值得到了,独占所标记住
*
*/
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//尝试获取锁,进入等待序列
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取AQS中volatile 的state值,再次使用CAS来获取锁,如果成功则标记
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//当前线程是否为state=0的线程,将state标记改为1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
Node链表
static final class Node {
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
//当前节点排队的线程
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
addWaiter(Node node)
AQS中的链表采用的是双向链表的结构
private Node addWaiter(Node mode) {
//构建一个链表节点Node
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//获取尾节点的引用,tail表示链表的尾节点
Node pred = tail;
//尾节点不为空,队列已经初始化过了
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//初始化队列,直到设置当前节点为尾节点才退出
enq(node);
return 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;
}
}
}
}
acquireQueued(final Node node, int arg)
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);
}
}
FairSync(公平锁)
//与非公平锁的区别在于 ,进入锁的时候就1 ,在尝试抢占锁的时候判断当前节点是否是头节点来判断
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//!hasQueuedPredecessors() 当前节点是否是head节点
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;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)