ReentrantLock和AQS的关系

首先我们来看看,如果用java并发包下的ReentrantLock来加锁和释放锁,是个什么样的:

1 ReentrantLock reentrantLock = new ReentrantLock();
2 reentrantLock.lock();
3 //业务代码
4 reentrantLock.unlock();

上面那段代码就是搞一个Lock对象,然后加锁和释放锁。那么,这个跟AQS有啥关系?关系大了去了,因为java并发包下很多API都是基于AQS来实现的加锁和释放锁等功能的,AQS是java并发包的基础类。上一部分源码:

 1 public class ReentrantLock implements Lock, java.io.Serializable {
2 private static final long serialVersionUID = 7373984872572414699L;
3 /** Synchronizer providing all implementation mechanics */
4 private final Sync sync;
5
6 /**
7 * Base of synchronization control for this lock. Subclassed
8 * into fair and nonfair versions below. Uses AQS state to
9 * represent the number of holds on the lock.
10 */
11 abstract static class Sync extends AbstractQueuedSynchronizer {
12 private static final long serialVersionUID = -5179523762034025860L;
13
14 /**
15 * Performs {@link Lock#lock}. The main reason for subclassing
16 * is to allow fast path for nonfair version.
17 */
18 abstract void lock();
19
20 /**
21 * Performs non-fair tryLock. tryAcquire is implemented in
22 * subclasses, but both need nonfair try for trylock method.
23 */
24 final boolean nonfairTryAcquire(int acquires) {
25 final Thread current = Thread.currentThread();
26 int c = getState();
27 if (c == 0) {
28 if (compareAndSetState(0, acquires)) {
29 setExclusiveOwnerThread(current);
30 return true;
31 }
32 }
33 else if (current == getExclusiveOwnerThread()) {
34 int nextc = c + acquires;
35 if (nextc < 0) // overflow
36 throw new Error("Maximum lock count exceeded");
37 setState(nextc);
38 return true;
39 }
40 return false;
41 }
42
43 protected final boolean tryRelease(int releases) {
44 int c = getState() - releases;
45 if (Thread.currentThread() != getExclusiveOwnerThread())
46 throw new IllegalMonitorStateException();
47 boolean free = false;
48 if (c == 0) {
49 free = true;
50 setExclusiveOwnerThread(null);
51 }
52 setState(c);
53 return free;
54 }
55
56 protected final boolean isHeldExclusively() {
57 // While we must in general read state before owner,
58 // we don't need to do so to check if current thread is owner
59 return getExclusiveOwnerThread() == Thread.currentThread();
60 }
61
62 final ConditionObject newCondition() {
63 return new ConditionObject();
64 }
65
66 // Methods relayed from outer class
67
68 final Thread getOwner() {
69 return getState() == 0 ? null : getExclusiveOwnerThread();
70 }
71
72 final int getHoldCount() {
73 return isHeldExclusively() ? getState() : 0;
74 }
75
76 final boolean isLocked() {
77 return getState() != 0;
78 }
79
80 /**
81 * Reconstitutes the instance from a stream (that is, deserializes it).
82 */
83 private void readObject(java.io.ObjectInputStream s)
84 throws java.io.IOException, ClassNotFoundException {
85 s.defaultReadObject();
86 setState(0); // reset to unlocked state
87 }
88 }
89 }

说白了,ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer类型的对象。这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性的核心组件。

ReentrantLock加锁和释放锁的底层原理

现在如果有一个线程过来尝试用ReentrantLock的lock()方法进行加锁,会发生什么事情呢?

 1 public abstract class AbstractQueuedSynchronizer
2 extends AbstractOwnableSynchronizer
3 implements java.io.Serializable {
4
5 /**
6 * The thread that enqueued this node. Initialized on
7 * construction and nulled out after use.
8 */
9 volatile Thread thread;
10
11 /**
12 * The synchronization state.
13 */
14 private volatile int state;
15
16 }

这个AQS对象内部有一个核心的变量叫做state,是int类型的,代表了加锁的状态。初始状态下,这个state的值是0。另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。接着线程1跑过来调用ReentrantLock的lock()方法尝试进行加锁,这个加锁的过程,直接就是用CAS操作将state值从0变为1。如果之前没人加过锁,那么state的值肯定是0,此时线程1就可以加锁成功。一旦线程1加锁成功了之后,就可以设置当前加锁线程是自己。

AQS就是并发包里的一个核心组件,里面有state变量、加锁线程变量等核心的东西,维护了加锁状态。ReentrantLock这种东西只是一个外层的API,内核中的锁机制实现都是依赖AQS组件的。这个ReentrantLock之所以用Reentrant打头,意思就是他是一个可重入锁。意思就是你可以对一个ReentrantLock对象多次执行lock()加锁和unlock()释放锁,也就是可以对一个锁加多次,叫做可重入加锁。大家看明白了那个state变量之后,就知道了如何进行可重入加锁!其实每次线程1可重入加锁一次,会判断一下当前加锁线程就是自己,那么他自己就可以可重入多次加锁,每次加锁就是把state的值给累加1,别的没啥变化,实现原理如下:

 1 public class ReentrantLock implements Lock, java.io.Serializable {
2 /**
3 * Sync object for non-fair locks
4 */
5 static final class NonfairSync extends Sync {
6 private static final long serialVersionUID = 7316153563782823691L;
7
8 /**
9 * Performs lock. Try immediate barge, backing up to normal
10 * acquire on failure.
11 */
12 final void lock() {
13 if (compareAndSetState(0, 1))
14 setExclusiveOwnerThread(Thread.currentThread());
15 else
16 acquire(1);
17 }
18
19 protected final boolean tryAcquire(int acquires) {
20 return nonfairTryAcquire(acquires);
21 }
22 }
23 }
24
25 public abstract class AbstractQueuedSynchronizer
26 extends AbstractOwnableSynchronizer
27 implements java.io.Serializable {
28
29 /**
30 * Acquires in exclusive mode, ignoring interrupts. Implemented
31 * by invoking at least once {@link #tryAcquire},
32 * returning on success. Otherwise the thread is queued, possibly
33 * repeatedly blocking and unblocking, invoking {@link
34 * #tryAcquire} until success. This method can be used
35 * to implement method {@link Lock#lock}.
36 *
37 * @param arg the acquire argument. This value is conveyed to
38 * {@link #tryAcquire} but is otherwise uninterpreted and
39 * can represent anything you like.
40 */
41 public final void acquire(int arg) {
42 if (!tryAcquire(arg) &&
43 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
44 selfInterrupt();
45 }
46
47 }

接着,如果线程1加锁了之后,线程2跑过来加锁会怎么样呢?我们来看看锁的互斥是如何实现的,线程2跑过来一下看到state的值不是0,所以CAS操作将state从0变为1的过程会失败,因为state的值当前为1,说明已经有人加锁了!接着线程2会看一下,是不是自己之前加的锁啊?当然不是了,“加锁线程”这个变量明确记录了是线程1占用了这个锁,所以线程2此时就是加锁失败。接着,线程2会将自己放入AQS中的一个等待队列,因为自己尝试加锁失败了,此时就要将自己放入队列中来等待,等待线程1释放锁之后,自己就可以重新尝试加锁了,所以大家可以看到,AQS是如此的核心。AQS内部还有一个等待队列,专门放那些加锁失败的线程。

 1 /**
2 * Condition implementation for a {@link
3 * AbstractQueuedSynchronizer} serving as the basis of a {@link
4 * Lock} implementation.
5 *
6 * <p>Method documentation for this class describes mechanics,
7 * not behavioral specifications from the point of view of Lock
8 * and Condition users. Exported versions of this class will in
9 * general need to be accompanied by documentation describing
10 * condition semantics that rely on those of the associated
11 * {@code AbstractQueuedSynchronizer}.
12 *
13 * <p>This class is Serializable, but all fields are transient,
14 * so deserialized conditions have no waiters.
15 */
16 public class ConditionObject implements Condition, java.io.Serializable {
17 private static final long serialVersionUID = 1173984872572414699L;
18 /** First node of condition queue. */
19 private transient Node firstWaiter;
20 /** Last node of condition queue. */
21 private transient Node lastWaiter;
22
23 /**
24 * Creates a new {@code ConditionObject} instance.
25 */
26 public ConditionObject() { }
27
28 // Internal methods
29
30 /**
31 * Adds a new waiter to wait queue.
32 * @return its new wait node
33 */
34 private Node addConditionWaiter() {
35 Node t = lastWaiter;
36 // If lastWaiter is cancelled, clean out.
37 if (t != null && t.waitStatus != Node.CONDITION) {
38 unlinkCancelledWaiters();
39 t = lastWaiter;
40 }
41 Node node = new Node(Thread.currentThread(), Node.CONDITION);
42 if (t == null)
43 firstWaiter = node;
44 else
45 t.nextWaiter = node;
46 lastWaiter = node;
47 return node;
48 }
49 }

接着,线程1在执行完自己的业务逻辑代码之后,就会释放锁,他释放锁的过程非常的简单,就是将AQS内的state变量的值递减1,如果state值为0,则彻底释放锁,会将“加锁线程”变量也设置为null!

 1 public class ReentrantLock implements Lock, java.io.Serializable {
2 /**
3 * Attempts to release this lock.
4 *
5 * <p>If the current thread is the holder of this lock then the hold
6 * count is decremented. If the hold count is now zero then the lock
7 * is released. If the current thread is not the holder of this
8 * lock then {@link IllegalMonitorStateException} is thrown.
9 *
10 * @throws IllegalMonitorStateException if the current thread does not
11 * hold this lock
12 */
13 public void unlock() {
14 sync.release(1);
15 }
16 }
17
18 public abstract class AbstractQueuedSynchronizer
19 extends AbstractOwnableSynchronizer
20 implements java.io.Serializable {
21 public final boolean release(int arg) {
22 if (tryRelease(arg)) {
23 Node h = head;
24 if (h != null && h.waitStatus != 0)
25 unparkSuccessor(h);
26 return true;
27 }
28 return false;
29 }
30 }

接下来,会从等待队列的队头唤醒线程2重新尝试加锁。好!线程2现在就重新尝试加锁,这时还是用CAS操作将state从0变为1,此时就会成功,成功之后代表加锁成功,就会将state设置为1。此外,还要把“加锁线程”设置为线程2自己,同时线程2自己就从等待队列中出队了。

其实一句话总结:AQS就是一个并发包的基础组件,用来实现各种锁,各种同步组件的。它包含了state变量、加锁线程、等待队列等并发中的核心组件。

Java多线程--AQS的更多相关文章

  1. Java多线程——AQS框架源码阅读

    AQS,全称AbstractQueuedSynchronizer,是Concurrent包锁的核心,没有AQS就没有Java的Concurrent包.它到底是个什么,我们来看看源码的第一段注解是怎么说 ...

  2. Java多线程:AQS

    在Java多线程:线程间通信之Lock中我们提到了ReentrantLock是API级别的实现,但是没有说明其具体实现原理.实际上,ReentrantLock的底层实现使用了AQS(AbstractQ ...

  3. Java多线程系列--AQS之 LockSupport

    concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS(JAVA CAS原理.unsafe.AQS)框架借助于两个类: Unsafe(提供CAS操作 ...

  4. Java多线程并发06——CAS与AQS

    在进行更近一步的了解Java锁的知识之前,我们需要先了解与锁有关的两个概念 CAS 与 AQS.关注我的公众号「Java面典」了解更多 Java 相关知识点. CAS(Compare And Swap ...

  5. Java多线程专题4: 锁的实现基础 AQS

    合集目录 Java多线程专题4: 锁的实现基础 AQS 对 AQS(AbstractQueuedSynchronizer)的理解 Provides a framework for implementi ...

  6. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  7. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  8. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

  9. Java多线程系列--“JUC锁”01之 框架

    本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...

随机推荐

  1. rabbit rpm地址

    rabbitmq 官方源: https://dl.bintray.com/rabbitmq/rpm/rabbitmq-server/ erlang 清华源(包含erlang所有版本): https:/ ...

  2. golang 并发demo 写入 redis

    原文链接:golang 并发demo 写入 redis 源代码: package main import ( "fmt" "runtime" "str ...

  3. python中eval, exec, execfile,和compile

    eval(str [,globals [,locals ]])函数将字符串str当成有效Python表达式来求值,并返回计算结果. 同样地, exec语句将字符串str当成有效Python代码来执行. ...

  4. 第6篇scrum冲刺(5.26)

    一.站立会议 1.照片 2.工作安排 成员 昨天已完成的工作 今天的工作安排 困难 陈芝敏   研究云开发,更新了登录模块,把用户的信息传入数据库了  学习云开发,云函数调用以及数据的前后端传递  遇 ...

  5. JavaScript 的诞生

    JavaScript的历史 网景 1993年出现网页浏览器NCSA Mosaic 1994年出现Netscape Navigator,并占据四分之三浏览器市场 1995年5月布兰登根据公司的要求发明一 ...

  6. MySQL优化--IO调度算法优化

    之前已经在微信公众号分享了数据库优化的方法,链接为https://mp.weixin.qq.com/s/6Atzk9UKPJRxxAs0nsKBXg . 其中操作系统部分介绍了IO调度算法的优化,本文 ...

  7. java前端知识点整理

    1.jsp内置对象?作用? request :客户端请求,包含来自 GET/POST,提供了几个用于获取 cookie, 和 session 的方法 response :网页传回用户端的回应 page ...

  8. 文件属性及find命令总结

    第1章   文件属性 1.1   文件的属性 1.1.1    查看文件的详细属性       PS:ls查看的文件或目录默认的是按照名字的第一个字母进行正序排序       ls 参数选项: -t ...

  9. 复杂一点的SQL语句:Oracle DDL和DML

    DDL:对表或者表的属性进行了改变 create:创建表创建用户创建视图 创建表 create table student(id int,score int) ; student后面与括号之间可以有空 ...

  10. Bitmap转ImageSource

    bitmap to bytes Bitmap b = new Bitmap( "test.bmp "); MemoryStream ms = new MemoryStream(); ...