先把我主要学习参考的文章放上来先,这篇文章讲的挺好的,分析比较到位,最好是先看完这篇文章,在接下去看我写的。不然你会一脸懵逼,不过等你看完这篇文章,可能我的文章对你也用途不大了

深入分析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基本原理的更多相关文章

  1. 并发和多线程(九)--AbstractQueuedSynchronizer排他锁基本原理

    AbstractQueuedSynchronizer简称为AQS,AQS是ReentrantLock.CountdownLatch.CycliBarrier等并发工具的原理/基础,所以了解AQS的原理 ...

  2. Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)

    AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...

  3. AbstractQueuedSynchronizer 原理分析 - 独占/共享模式

    1.简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架,比如 ...

  4. AbstractQueuedSynchronizer 原理分析 - 独占/共享模式(转)

    1.简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架,比如 ...

  5. JUC同步器框架AbstractQueuedSynchronizer源码图文分析

    JUC同步器框架AbstractQueuedSynchronizer源码图文分析 前提 Doug Lea大神在编写JUC(java.util.concurrent)包的时候引入了java.util.c ...

  6. java 并发(五)---AbstractQueuedSynchronizer(4)

    问题 : rwl 的底层实现是什么,应用场景是什么 读写锁 ReentrantReadWriteLock 首先我们来了解一下 ReentrantReadWriteLock 的作用是什么?和 Reent ...

  7. java 并发(五)---AbstractQueuedSynchronizer(3)

           文章代码分析和部分图片来自参考文章 问题 : CountDownLatch  和 CyclicBarrier 的区别 认识 CountDownLatch 分析这个类,首先了解一下它所可以 ...

  8. java 并发(五)---AbstractQueuedSynchronizer(2)

           文章部分代码和照片来自参考资料 问题 : ConditionObject  的 await 和 signal 方法是如何实现的 ConditonObject ConditionObjec ...

  9. java 并发(五)---AbstractQueuedSynchronizer

    文章部分图片和代码来自参考文章. LockSupport 和 CLH 和 ConditionObject 阅读源码首先看一下注解 ,知道了大概的意思后,再进行分析.注释一开始就进行了概括.AQS的实现 ...

随机推荐

  1. wavenet重要概念

    带洞因果卷积 https://img-blog.csdn.net/20181021210509222?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dl ...

  2. .NET编程5月小结 - Blazor, Unity, Dependency Injection

    本文是我在5月份看到的一些有趣的内容的集合.在这里你可以找到许多有关Blazor.ASPNET Core的学习资源和示例项目,有关在Unity中使用Zenject进行单元测试的博客,有关Unity项目 ...

  3. Redis 入门到分布式 (八)Redis Sentinel

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) sentinel-目录 主从复制高可用 安装配置 实现原理 架构说明 客户端连接 常见开发运维问题 一. ...

  4. Java实现 LeetCode 115 不同的子序列

    115. 不同的子序列 给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数. 一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字 ...

  5. Java实现有向图强连通分量的Tarjan算法

    1 问题描述 引用自百度百科: 如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.有向图的极大强连通子图,称为 ...

  6. Java实现第九届蓝桥杯星期一

    星期一 整个20世纪(1901年1月1日至2000年12月31日之间),一共有多少个星期一? (不要告诉我你不知道今天是星期几) 注意:需要提交的只是一个整数,不要填写任何多余的内容或说明文字. 解: ...

  7. java实现第六届蓝桥杯熊怪吃核桃

    熊怪吃核桃 题目描述 森林里有一只熊怪,很爱吃核桃.不过它有个习惯,每次都把找到的核桃分成相等的两份,吃掉一份,留一份.如果不能等分,熊怪就会扔掉一个核桃再分.第二天再继续这个过程,直到最后剩一个核桃 ...

  8. Linux帮助命令man详解

    命令man详解 命令man,可以获得命令(使用whatis命令可以得到一个命令的简短介绍,可以使用:命令 --help 来获得命令的选项说明)或配置文件的帮助信息(可以使用apropos命令仅查看配置 ...

  9. PAT 在霍格沃茨找零钱

    如果你是哈利·波特迷,你会知道魔法世界有它自己的货币系统 —— 就如海格告诉哈利的:“十七个银西可(Sickle)兑一个加隆(Galleon),二十九个纳特(Knut)兑一个西可,很容易.”现在,给定 ...

  10. PAT 挖掘机技术哪家强

    为了用事实说明挖掘机技术到底哪家强,PAT 组织了一场挖掘机技能大赛.现请你根据比赛结果统计出技术最强的那个学校. 输入格式: 输入在第 1 行给出不超过 105 的正整数 N,即参赛人数.随后 N  ...