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

深入分析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. 最小生成树——Prim算法理解

    背景:本文是在小甲鱼数据结构教学视频中的代码的基础上,添加详细注释而完成的.该段代码并不完整,仅摘录了核心算法部分,结合自己的思考,谈谈理解. Prim算法理解: 如图(摘录自小甲鱼教学视频中的图片) ...

  2. no-strings-attached

    0x01 拿到程序,直接IDA放进去看一下,结果如图: 我们发现main中有四个函数,我们一个一个进去,经过分析之后,发现authenticate();是关键函数,所以,接下来我们进去看看. void ...

  3. Rocket - debug - TLDebugModuleInner - Hart Bus Access

    https://mp.weixin.qq.com/s/deNMEyJ1idJDVoZwwo0A1A 简单介绍TLDebugModuleInner中核心总线访问(Hart Bus Access). 参考 ...

  4. 跨域解决方案 - 跨域资源共享cors

    目录 1. cors 介绍 2. 原理 3. cors 解决跨域 4. 自定义HTTP 头部字段解决跨域 5. 代码演示 5. 参考链接 1. cors 介绍 cors 说的是一个机制,其实相当于一个 ...

  5. Java实现 LeetCode 481 神奇字符串

    481. 神奇字符串 神奇的字符串 S 只包含 '1' 和 '2',并遵守以下规则: 字符串 S 是神奇的,因为串联字符 '1' 和 '2' 的连续出现次数会生成字符串 S 本身. 字符串 S 的前几 ...

  6. 第五届蓝桥杯JavaC组省赛真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.猜年龄 题目描述 小明带两个妹妹参加元宵灯会.别人问她们多大了,她们调皮地说:"我们俩的年龄之积是年龄之和的6倍" ...

  7. Java实现 洛谷 P1601 A+B Problem(高精)

    import java.util.*; import java.math.*; public class Main { public static void main(String args[]) { ...

  8. java解决poi导出excel文字水印,导出excel不可操作问题

    首先需求是用户提出导出excel数据需使用水印备注其用途: 其实就是在导出excel的同时带有自定义文字水印的导出. 那么我们首先想到的肯定是以一个什么样的思路去解决该问题,首先查找poi导出exce ...

  9. UBoot配置编译及Makefile分析

    一. UBoot配置编译初步分析 1. UBoot源码结构 (1)UBoot工程项目中的文件可以分为3类 ① 第1类目录:与处理器体系结构或开发板硬件直接相关 ② 第2类目录:一些通用的函数或驱动程序 ...

  10. k8s学习-Service

    4.4.Service 可能会用到ipvs,先安装: yum install -y openssl openssl-devel popt popt-devel libnl-devel kenel-de ...