AbstractQueuedSynchronizer和ReentranLock基本原理
先把我主要学习参考的文章放上来先,这篇文章讲的挺好的,分析比较到位,最好是先看完这篇文章,在接下去看我写的。不然你会一脸懵逼,不过等你看完这篇文章,可能我的文章对你也用途不大了
深入分析AbstractQueuedSynchronizer独占锁的实现原理:ReentranLock
小弟我也是刚开始研究这个Lock锁相关,如果哪里写的有问题,希望各位大哥大姐帮忙指出,谢谢。
正文:
AbstractQueuedSynchronizer(简称AQS),抽象队列同步器,AQS是整个Concurrent包中最核心的地方,其它的并发工具也都是使用AQS来实现的,我们所熟悉的ConcurrentHashMap也是在这玩意的基础上实现的同步。
在深入学习之前,我们先来看看Concurrent包里面有什么东东:
这篇文章只会讲AbstractQueuedSynchronizer和ReentrantLock,而且也只讲加锁和放锁。
大概说一下加锁的原理:
AbstractQueuedSynchronizer 有一个state字段,当这个字段为0时,就代表当前锁对象可以被线程获取到锁;相反,如果state这个字段不为0,那当前锁对象就不能被线程获取到锁。state这个字段就相当于sychronized关键字所用的monitor。
AbstractQueuedSynchronizer有一个链表队列,这个队列每个节点都是一个等待获取锁的线程节点。
当线程尝试获取锁时,先直接尝试获取锁(也就是判断state是不是为0),如果获取不到锁,那就向队尾插入一个新节点,并且当前线程不断循环去获取锁(一定次数获取不到就会挂起线程,直到被别的线程唤醒)。
AbstractQueuedSynchronizer的state字段是用volatile进行修饰的(保证了对所有线程可见),同时对state的修改是使用compareAndSet进行乐观锁修改的,正是因为这两点,才使得不用sychronized也能在多线程下保证线程安全。
释放锁:
释放锁其实更简单,就是讲 state减少至0,正常来讲,每次加锁都是对state进行加1操作,对应的每次释放锁都是对state进行减1操作。
一个线程可以对一个锁进行重复次加锁,state可以一直加加加,但是当这个线程不在需要这个锁时,就必须将这个锁的state减少至0,也就是说你用了多少次lock(),就必须用多少次unLock(),否则这把锁就出错了,报废了,
再也没有线程可以获取到锁了,所有线程都卡死在这了。这一点和sychronized不同,sychronized会自动加锁放锁,但是lock必须自己手动进行。
另外,当一个线程将锁都释放完后,会唤醒其他等待这把锁,但是被挂起了的线程,让他们重新去获取锁。
AbstractQueuedSynchronizer里面比较关键的代码:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer{ protected AbstractQueuedSynchronizer() { } /**
* 这玩意会形成一个双向队列连表,用于保存等待锁的线程。
* 队列首节点就是获取到锁的线程节点,新增线程请求锁,会加入到链表尾
* */
static final class Node {
/** 共享模式节点 */
static final Node SHARED = new Node();
/** 独占模式节点 */
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 */
// 只有当前节点的前一个节点为SIGNAL时,才能当前节点才能被挂起。
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; /**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
// 线程的等待状态
volatile int waitStatus; volatile Node prev; volatile Node next; /**
* 当前节点的线程,也就是请求锁的线程
*/
volatile Thread thread; /**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
Node nextWaiter; /**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
} /**
* 获取前一个节点
*/
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;
}
} /**
* 链表队列首节点
*/
private transient volatile Node head; /**
* 链表队列尾节点
*/
private transient volatile Node tail; /**
* 同步状态值,注意,这个值就是锁的关键,也就是线程打生打死都在抢的东西
* 我们所谓的获取锁,就是state这个值为0的时候给他加1
* 释放锁就是把state的值减1
* 当这个值重新变成0时,锁就被彻底释放,其他线程就可以获取锁了。
* 如果state不为0,那是不管怎样都抢不到锁的
*
* 可能会有人问,state仅用volatile修饰,还是线程不安全的。所以,所有关于volatile修饰的变量
* 在这里都是通过compareAndSet来进行修改的
*/
private volatile int state; /**
* 独占锁的线程,这个属性是父类AbstractOwnableSynchronizer的,这里需要注意
* 在线程抢到锁后会将这个属性设成自己的线程
* */
private transient Thread exclusiveOwnerThread; /**
* 请求获取锁。
* AbstractQueuedSynchronizer请求锁是非公平锁。
* 什么是公平锁什么是非公平锁呢?
* 公平锁就是先来先拿锁,后来得排队。
* 非公平锁就是后来者先尝试插队取锁,如果别人还没用完锁并归还,那他就只能乖乖排队。
* 两者的唯一差别就是非公平锁会先进行一次获取锁操作,失败就和公平锁一样排队获取。
* */
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 如果插不了队取锁,就新增一个节点去连表队列尾部排队获取锁
selfInterrupt();
} /**
* 这个方法是子类必须实现的,用于具体获取锁
* */
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
} /**
* 队列节点自旋获取锁。
* 什么叫自旋呢?自旋就是自己一直循环执行
* */
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
/**
* 在队列里获取锁是按先后顺序进行获取的
* 因为队列首节点就是获取到锁的线程,所以首节点后的一个节点就是下一个获取锁的线程,所以这里要求p == head,
* 这样即便多线程下,别的线程因为不是第二个节点,所以也不能去抢锁
* 另外首节点有可能还在执行,也有可能执行完了并释放了锁,所以还要尝试获取一次锁tryAcquire(arg)
* */
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 如果抢不到锁,他会判断自己这个线程是否需要进行挂起,不浪费CPU资源
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
} /**
* 抢锁失败后判断是否需要挂起当前线程
* 他是根据前一个节点进行判断的
* */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 只有前一个节点为SIGNAL的节点,才会将线程挂起,其他线程等待状态都会在尝试一次
// 绝大多数线程在多次获取不到锁后,都会被置为SIGNAL这个状态,然后挂起
if (ws == Node.SIGNAL)
/*
* 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 {
/*
* 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);
}
return false;
} /**
* 挂起线程,让他休眠,有点类似于wait方法,CPU将不再调用他,需要对应的unpark方法进行唤醒,unpark会在获取到锁的线程
* 释放锁或者取消后才调用
* */
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
} /**
* 释放锁,如果释放锁成功了,就会调用unparkSuccessor来唤醒其他休眠的线程
* */
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
} /**
* 释放锁的具体方法,子类必须实现
* */
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
} /***/
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.
*/
int ws = node.waitStatus;
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;
// 从链表尾开始一路往前遍历,找到最前面的非cancelled的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 唤醒线程
if (s != null)
LockSupport.unpark(s.thread);
}
}
ReentrantLock 的一些关键代码:
public class ReentrantLock implements Lock, java.io.Serializable {
/**
* 同步器,继承自AbstractQueuedSynchronizer
* */
private final Sync sync; /**
* 这个锁同步器可以是公平锁也可以非公平锁
* */
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L; /**
* 子类必须实现上锁方法
*/
abstract void lock(); /**
* 非公平锁获取锁
*/
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;
} /**
* 释放锁,释放锁没有公平和非公平的说法,都一样
* */
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
} /**
* 非公平锁同步器
* */
static final class NonfairSync extends Sync { /**
* 获取锁,这个方法会一直阻塞,知道获取到锁为止,或者被取消
*/
final void lock() {
// 因为是非公平的,所以会先尝试一次获取锁,失败就调用父类的acquire方法排队获取
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
/**
* 尝试获取锁,只尝试一次,成功就成功,失败就失败
* */
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 公平锁同步器
* */
static final class FairSync extends Sync {
/**
* 因为这是公平锁,所以他直接就进入队列进行排队
* */
final void lock() {
/**
* 当然,因为AbstractQueuedSynchronizer的acquire本身就是会尝试先获取一次锁,失败才排队,所以严格来说他还是非公平的
* */
acquire(1);
} /**
* 如果没有别的线程在排队,他才会去获取锁,否则都不获取
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
} /**
* ReentrantLock默认就是非公平锁
* */
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* ReentrantLock也支持公平锁
* */
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
/**
* 获取锁
* */
public void lock() {
sync.lock();
}
/**
* 尝试加锁
* */
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
/**
* 释放锁,这里是直接用AbstractQueuedSynchronizer的释放方法
* */
public void unlock() {
sync.release(1);
}
}
AbstractQueuedSynchronizer和ReentranLock基本原理的更多相关文章
- 并发和多线程(九)--AbstractQueuedSynchronizer排他锁基本原理
AbstractQueuedSynchronizer简称为AQS,AQS是ReentrantLock.CountdownLatch.CycliBarrier等并发工具的原理/基础,所以了解AQS的原理 ...
- Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)
AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...
- AbstractQueuedSynchronizer 原理分析 - 独占/共享模式
1.简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架,比如 ...
- AbstractQueuedSynchronizer 原理分析 - 独占/共享模式(转)
1.简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架,比如 ...
- JUC同步器框架AbstractQueuedSynchronizer源码图文分析
JUC同步器框架AbstractQueuedSynchronizer源码图文分析 前提 Doug Lea大神在编写JUC(java.util.concurrent)包的时候引入了java.util.c ...
- java 并发(五)---AbstractQueuedSynchronizer(4)
问题 : rwl 的底层实现是什么,应用场景是什么 读写锁 ReentrantReadWriteLock 首先我们来了解一下 ReentrantReadWriteLock 的作用是什么?和 Reent ...
- java 并发(五)---AbstractQueuedSynchronizer(3)
文章代码分析和部分图片来自参考文章 问题 : CountDownLatch 和 CyclicBarrier 的区别 认识 CountDownLatch 分析这个类,首先了解一下它所可以 ...
- java 并发(五)---AbstractQueuedSynchronizer(2)
文章部分代码和照片来自参考资料 问题 : ConditionObject 的 await 和 signal 方法是如何实现的 ConditonObject ConditionObjec ...
- java 并发(五)---AbstractQueuedSynchronizer
文章部分图片和代码来自参考文章. LockSupport 和 CLH 和 ConditionObject 阅读源码首先看一下注解 ,知道了大概的意思后,再进行分析.注释一开始就进行了概括.AQS的实现 ...
随机推荐
- 多服务器 vps 管理方法,十款远程桌面软件测评分析
作为运维工作者,从几年前巨大的工作量到如今有了极大的改善,这些年试用的远程桌面管理软件不在少数. 远程桌面是微软公司为了方便网络管理员管理维护服务器而推出的一项服务.从windows 2000 ser ...
- jQuery-语言基础整理
jQuery是js的一个类库,主要封装的是js中DOM操作部分,使用和原生js一样 1.需要先引入页面才可以使用 代码引入:<script src='jquery.js'></scr ...
- Rocket - debug - Example: Write Memory
https://mp.weixin.qq.com/s/on1LugO9fTFJstMes3T2Xg 介绍riscv-debug的使用实例:使用三种方法写内存. 1. Using System Bus ...
- Spring Boot入门系列(十四)使用JdbcTemplate操作数据库,配置多数据源!
前面介绍了Spring Boot 中的整合Mybatis并实现增删改查.如何实现事物控制.不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/c ...
- Java实现 LeetCode 423 从英文中重建数字
423. 从英文中重建数字 给定一个非空字符串,其中包含字母顺序打乱的英文单词表示的数字0-9.按升序输出原始的数字. 注意: 输入只包含小写英文字母. 输入保证合法并可以转换为原始的数字,这意味着像 ...
- Java实现 计蒜客 拯救行动
拯救行动 公主被恶人抓走,被关押在牢房的某个地方.牢房用 N \times M (N, M \le 200)N×M(N,M≤200) 的矩阵来表示.矩阵中的每项可以代表道路(@).墙壁(#).和守卫( ...
- XStrea学习手册
一.前言 1.XStream官网 http://x-stream.github.io 2.XStream是什么 XStream是一个简单的基于Java的类库,用来将Java对象序列化成XML(J ...
- DirectX11 With Windows SDK--31 阴影映射
前言 阴影既暗示着光源相对于观察者的位置关系,也从侧面传达了场景中各物体之间的相对位置.本章将起底最基础的阴影映射算法,而像复杂如级联阴影映射这样的技术,也是在阴影映射的基础上发展而来的. 学习目标: ...
- 更多的bash shell命令
1.探查进程:ps 2.实时监控进程:top 3.结束进程:kill.killall 4.查看挂载媒体:mount 5.移除设备:nmount 6.查看已挂载设备的使用情况:df 7.显示特定磁盘的使 ...
- python自学Day06(自学书籍python编程从入门到实践)
第7章 用户输入和while循环 我们设计的程序大多是为了解决用户最终的问题,所以我们大多需要在用户那里获取一些信息. 学习用户输入的获取与处理,学习while循环让程序不断运行直到达到指定的条件不满 ...