问题

(1)AQS是什么?

(2)AQS的定位?

(3)AQS的实现原理?

(4)基于AQS实现自己的锁?

简介

AQS的全称是AbstractQueuedSynchronizer,它的定位是为Java中几乎所有的锁和同步器提供一个基础框架。

AQS是基于FIFO的队列实现的,并且内部维护了一个状态变量state,通过原子更新这个状态变量state即可以实现加锁解锁操作。

本章及后续章节的内容理解起来可能会比较晦涩,建议先阅读彤哥上一章的内容【死磕 java同步系列之自己动手写一个锁Lock】。

核心源码

主要内部类

static final class Node {
// 标识一个节点是共享模式
static final Node SHARED = new Node();
// 标识一个节点是互斥模式
static final Node EXCLUSIVE = null; // 标识线程已取消
static final int CANCELLED = 1;
// 标识后继节点需要唤醒
static final int SIGNAL = -1;
// 标识线程等待在一个条件上
static final int CONDITION = -2;
// 标识后面的共享锁需要无条件的传播(共享锁需要连续唤醒读的线程)
static final int PROPAGATE = -3; // 当前节点保存的线程对应的等待状态
volatile int waitStatus; // 前一个节点
volatile Node prev; // 后一个节点
volatile Node next; // 当前节点保存的线程
volatile Thread thread; // 下一个等待在条件上的节点(Condition锁时使用)
Node nextWaiter; // 是否是共享模式
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
// 把共享模式还是互斥模式存储到nextWaiter这个字段里面了
this.nextWaiter = mode;
this.thread = thread;
} // 节点的构造方法
Node(Thread thread, int waitStatus) { // Used by Condition
// 等待的状态,在Condition中使用
this.waitStatus = waitStatus;
this.thread = thread;
}
}

典型的双链表结构,节点中保存着当前线程、前一个节点、后一个节点以及线程的状态等信息。

主要属性

// 队列的头节点
private transient volatile Node head;
// 队列的尾节点
private transient volatile Node tail;
// 控制加锁解锁的状态变量
private volatile int state;

定义了一个状态变量和一个队列,状态变量用来控制加锁解锁,队列用来放置等待的线程。

注意,这几个变量都要使用volatile关键字来修饰,因为是在多线程环境下操作,要保证它们的值修改之后对其它线程立即可见。

这几个变量的修改是直接使用的Unsafe这个类来操作的:

// 获取Unsafe类的实例,注意这种方式仅限于jdk自己使用,普通用户是无法这样调用的
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 状态变量state的偏移量
private static final long stateOffset;
// 头节点的偏移量
private static final long headOffset;
// 尾节点的偏移量
private static final long tailOffset;
// 等待状态的偏移量(Node的属性)
private static final long waitStatusOffset;
// 下一个节点的偏移量(Node的属性)
private static final long nextOffset; static {
try {
// 获取state的偏移量
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
// 获取head的偏移量
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
// 获取tail的偏移量
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
// 获取waitStatus的偏移量
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
// 获取next的偏移量
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next")); } catch (Exception ex) { throw new Error(ex); }
} // 调用Unsafe的方法原子更新state
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

关于Unsafe类的讲解请参考彤哥之前写的【死磕 java魔法类之Unsafe解析】。

子类需要实现的主要方法

我们可以看到AQS的全称是AbstractQueuedSynchronizer,它本质上是一个抽象类,说明它本质上应该是需要子类来实现的,那么子类实现一个同步器需要实现哪些方法呢?

// 互斥模式下使用:尝试获取锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 互斥模式下使用:尝试释放锁
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// 共享模式下使用:尝试获取锁
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
// 共享模式下使用:尝试释放锁
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
// 如果当前线程独占着锁,返回true
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}

问题:这几个方法为什么不直接定义成抽象方法呢?

因为子类只要实现这几个方法中的一部分就可以实现一个同步器了,所以不需要定义成抽象方法。

下面我们通过一个案例来介绍AQS中的部分方法。

基于AQS自己动手写一个锁

直接上代码:

public class MyLockBaseOnAqs {

    // 定义一个同步器,实现AQS类
private static class Sync extends AbstractQueuedSynchronizer {
// 实现tryAcquire(acquires)方法
@Override
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 实现tryRelease(releases)方法
@Override
protected boolean tryRelease(int releases) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
} // 声明同步器
private final Sync sync = new Sync(); // 加锁
public void lock() {
sync.acquire(1);
} // 解锁
public void unlock() {
sync.release(1);
} private static int count = 0; public static void main(String[] args) throws InterruptedException {
MyLockBaseOnAqs lock = new MyLockBaseOnAqs(); CountDownLatch countDownLatch = new CountDownLatch(1000); IntStream.range(0, 1000).forEach(i -> new Thread(() -> {
lock.lock(); try {
IntStream.range(0, 10000).forEach(j -> {
count++;
});
} finally {
lock.unlock();
}
// System.out.println(Thread.currentThread().getName());
countDownLatch.countDown();
}, "tt-" + i).start()); countDownLatch.await(); System.out.println(count);
}
}

运行main()方法总是打印出10000000(一千万),说明这个锁也是可以直接使用的,当然这也是一个不可重入的锁。

是不是很简单,只需要简单地实现AQS的两个方法就完成了上一章彤哥自己动手实现的锁的功能。

它是怎么实现的呢?

我们这一章先不讲源码,后面学习了ReentrantLock自然就明白了。

总结

这一章就到此结束了,本篇没有去深入的解析AQS的源码,笔者认为这没有必要,因为对于从来都没有看过锁相关的源码的同学来说,一上来就讲AQS的源码肯定会一脸懵逼的,具体的源码我们穿插在后面的锁和同步器的部分来学习,等所有跟AQS相关的源码学习完毕了,再来一篇总结。

下面总结一下这一章的主要内容:

(1)AQS是Java中几乎所有锁和同步器的一个基础框架,这里说的是“几乎”,因为有极个别确实没有通过AQS来实现;

(2)AQS中维护了一个队列,这个队列使用双链表实现,用于保存等待锁排队的线程;

(3)AQS中维护了一个状态变量,控制这个状态变量就可以实现加锁解锁操作了;

(4)基于AQS自己动手写一个锁非常简单,只需要实现AQS的几个方法即可。

彩蛋

上一章彤哥自己动手写的锁,其实可以看成是AQS的一个缩影,看懂了那个基本上AQS可以看懂一半了,因为彤哥那个里面没有写Condition相关的内容,下一章ReentrantLock重入锁中我们将一起学习Condition相关的内容。

所以呢,还是建议大家去看看这篇文章,点击下面的推荐阅读可以直达。

推荐阅读

  1. 死磕 java同步系列之自己动手写一个锁Lock

  2. 死磕 java魔法类之Unsafe解析

  3. 死磕 java同步系列之JMM(Java Memory Model)

  4. 死磕 java同步系列之volatile解析

  5. 死磕 java同步系列之synchronized解析


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java同步系列之AQS起篇的更多相关文章

  1. 死磕 java同步系列之AQS终篇(面试)

    问题 (1)AQS的定位? (2)AQS的重要组成部分? (3)AQS运用的设计模式? (4)AQS的总体流程? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为 ...

  2. 死磕 java同步系列之CyclicBarrier源码解析——有图有真相

    问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...

  3. 死磕 java同步系列之Phaser源码解析

    问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...

  4. 死磕 java同步系列之zookeeper分布式锁

    问题 (1)zookeeper如何实现分布式锁? (2)zookeeper分布式锁有哪些优点? (3)zookeeper分布式锁有哪些缺点? 简介 zooKeeper是一个分布式的,开放源码的分布式应 ...

  5. 死磕 java同步系列之redis分布式锁进化史

    问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...

  6. 死磕 java同步系列之终结篇

    简介 同步系列到此就结束了,本篇文章对同步系列做一个总结. 脑图 下面是关于同步系列的一份脑图,列举了主要的知识点和问题点,看过本系列文章的同学可以根据脑图自行回顾所学的内容,也可以作为面试前的准备. ...

  7. 死磕 java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  8. 死磕 java同步系列之Semaphore源码解析

    问题 (1)Semaphore是什么? (2)Semaphore具有哪些特性? (3)Semaphore通常使用在什么场景中? (4)Semaphore的许可次数是否可以动态增减? (5)Semaph ...

  9. 死磕 java同步系列之ReentrantReadWriteLock源码解析

    问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的 ...

随机推荐

  1. javascript实现原生ajax的几种方法介绍

    自从javascript有了各种框架之后,比如jquery,使用ajax已经变的相当简单了.但有时候为了追求简洁,可能项目中不需要加载jquery这种庞大的js插件.但又要使用到ajax这种功能该如何 ...

  2. 03 Django视图

    功能 接受Web请求HttpRequest,进行逻辑处理,与 M 和 T 进行交互,返回 Web 响应 HttpResponse 给请求者 示例项目的创建 创建项目 test3 django-admi ...

  3. LeetCode(206) Reverse Linked List

    题目 Reverse a singly linked list. click to show more hints. Hint: A linked list can be reversed eithe ...

  4. iOS 中的视图函数 init initwithnib viewDidLoad viewWillAppear的总结

    我要总结的函数主要是这几个: UIView *view-如果view还没有被初始化的话,getter方法会先调用[self loadView],如果getter或者setter方法被重写了,子类中的g ...

  5. 2018 Multi-University Training Contest 1 Balanced Sequence(贪心)

    题意: t组测试数据,每组数据有 n 个只由 '(' 和 ')' 构成的括号串. 要求把这 n 个串排序然后组成一个大的括号串,使得能够匹配的括号数最多. 如()()答案能够匹配的括号数是 4,(() ...

  6. 线段树:CDOJ1591-An easy problem A (RMQ算法和最简单的线段树模板)

    An easy problem A Time Limit: 1000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others) Pr ...

  7. BZOJ 4355: Play with sequence

    调了好久,还是黑盒测试有前途 我以前怕不是学了假的吉利线段树(我第一次知道还要记次小值去更新的........) #include<cstdio> #include<algorith ...

  8. POJ 3057 网络流 Evacuation

    题意: 有一个n×m的房间,四周每个格子要么是墙要么是门.中间部分是墙或者人. 现在所有人要从房间逃出去,每个人的速度为1,也就是每个单位时间只能向上下左右四个方向走一格. 多个人可以站在同一个格子上 ...

  9. CodeForces 543D 树形DP Road Improvement

    题意: 有一颗树,每条边是好边或者是坏边,对于一个节点为x,如果任意一个点到x的路径上的坏边不超过1条,那么这样的方案是合法的,求所有合法的方案数. 对于n个所有可能的x,输出n个答案. 分析: 题解 ...

  10. git仓库删除所有提交历史记录

    stackoverflow原问题地址:http://stackoverflow.com/questions/13716658/how-to-delete-all-commit-history-in-g ...