详解JDK锁02:万字文!结合实战案例,手撕AQS源码!

简介: 详解JDK锁02:万字文!结合实战案例,手撕AQS源码!

详解JDK锁02:AQS

1. AQS简述

这一部分,我将从是什么、干什么、怎么用三个角度简单讲述一下AQS

1.1 是什么?

AQS全称为AbstractQueuedSynchronizer,中文名称为队列同步器。

拆分一下中文就可知,一定离不开 队列同步 这两个概念,下面进一步讲解其作用。

1.2 干什么?

AQS是用来构建锁或者其他同步组件的基础框架。

学Java并发的话就重点关住于AQS是如何构建锁的,因为同步器是实现锁的关键!

  1. AQS用一个 int 成员变量来表示同步状态。通过修改同步状态,以此达到获取锁与释放锁的目的。
    比如说一个线程获取到了锁,那么就相当于它此时获取到了同步状态。
    一个线程执行完了它的任务,它去释放锁,就相当于释放同步状态。
  2. AQS通过内置的 FIFO队列 完成线程的排队工作。这一点其实并不难理解。当一个线程获取锁失败之后,可以选择陷入阻塞状态,也可以进行非阻塞地自旋重试;当有多个线程独占式地去获取锁时,只有一个线程可以获取成功,其它均会失败。那么应当如何管理这些竞争失败的锁呢?这便是队列的作用。
  • 线程获取锁失败时,便进行入队列操作,成为队列的尾结点,进入等待状态

1.3 怎么用?

AQS的实现方式是继承:子类通过继承同步器并实现它的抽象方法来管理同步状态。

AQS支持独占式地获取同步状态与共享式地获取同步状态

其实AQS的精髓就在于它简化了锁的实现方式,我们不需要关心同步状态管理、线程排队、等待与唤醒等底层操作逻辑,我们只需要将精力放在锁的核心功能:加锁与解锁。

可以这样理解:

  • 锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节
  • AQS是面向锁的实现者,它定义了锁的实现者与同步器交互的接口,隐藏了实现细节

之后会写一个实战案例去用AQS实现一个锁。

2. AQS方法简述

下面三个方法是用于管理同步状态

  1. getState():用于获取同步状态
  2. setState(int newState):用于设置同步状态
  3. compareAndSetState(int expect, int update):使用CAS设置同步状态,保证原子性

之前第一部分提到:AQS已经帮我们实现了队列的维护逻辑,我们实现锁时只需要重写获取锁的方法

  1. tryAcquire(int arg):独占式地获取同步状态,返回值为布尔类型,true为获取成功,false为获取失败。
  2. tryRelease(int arg):独占式地释放同步状态,返回值为布尔类型,true为释放成功,false为释放失败。
  3. tryAcquireShared(int arg):共享式地获取同步状态,返回值为int类型。
  • 返回0表示成功,且没有剩余资源
  • 返回大于0的值表示成功,仍有剩余资源
  • 返回负数代表获取失败
  1. tryReleaseShared(int arg):共享式地释放同步状态,返回值为布尔类型。
  • 如果释放后允许唤醒后续等待节点时,返回true;否则返回false
  1. isHeldExclusively():当前同步器是否被线程独占

通过对于这些方法进行简单理解,便能初步体会到:

  • 当同步状态state为0时,其他线程才有可能获取到同步状态,即获取到锁。
  • 对于可重入锁,当线程独占锁之后,会将同步状态state进行自增。如果该线程一直重复地获取该锁,则state会一直累加;该线程去释放该锁时,必须将state自减到0,才算是完全释放成功。

3. AQS实战案例

通过使用AQS,简单地实现一个独占不可重入锁,也就是说该锁的state只有0与1两种状态。

重点关注继承自AQS的Sync内部类,这里面自定义了获取同步状态与释放同步状态的核心逻辑。

这个案例印证了这句话:AQS是面向锁的实现者,它定义了锁的实现者与同步器交互的接口,隐藏了实现细节


class ExclusiveLock implements Lock {
    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            // 当通过CAS设置state为1时,代表加锁成功
            if (compareAndSetState(0 ,1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        @Override
        protected boolean tryRelease(int arg) {
            // 释放锁时如果发现该锁已被释放,说明有异常
            if (getState() == 0) throw new IllegalArgumentException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        // 当state==1时表示处于占用状态
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
        public Condition newCondition() {
            return new ConditionObject();
        }
    }
    private final Sync sync = new Sync();
    @Override
    public void lock() {
        sync.acquire(1);
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }
    @Override
    public void unlock() {
        sync.release(1);
    }
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}
相关文章
|
4月前
|
安全 前端开发 Java
JDK源码级别彻底剖析JVM类加载机制
JDK源码级别彻底剖析JVM类加载机制
|
4月前
|
缓存 Dubbo Java
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
|
1月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
18天前
|
Oracle Java 关系型数据库
CentOS 7.6操作系统部署JDK实战案例
这篇文章介绍了在CentOS 7.6操作系统上通过多种方式部署JDK的详细步骤,包括使用yum安装openjdk、基于rpm包和二进制包安装Oracle JDK,并提供了配置环境变量的方法。
174 80
|
14天前
|
Oracle Java 关系型数据库
【颠覆性升级】JDK 22:超级构造器与区域锁,重塑Java编程的两大基石!
【9月更文挑战第6天】JDK 22的发布标志着Java编程语言在性能和灵活性方面迈出了重要的一步。超级构造器和区域锁这两大基石的引入,不仅简化了代码设计,提高了开发效率,还优化了垃圾收集器的性能,降低了应用延迟。这些改进不仅展示了Oracle在Java生态系统中的持续改进和创新精神,也为广大Java开发者提供了更多的可能性和便利。我们有理由相信,在未来的Java编程中,这些新特性将发挥越来越重要的作用,推动Java技术不断向前发展。
|
27天前
|
算法 安全 Java
深入JDK源码:揭开ConcurrentHashMap底层结构的神秘面纱
【8月更文挑战第24天】`ConcurrentHashMap`是Java并发编程中不可或缺的线程安全哈希表实现。它通过精巧的锁机制和无锁算法显著提升了并发性能。本文首先介绍了早期版本中使用的“段”结构,每个段是一个带有独立锁的小型哈希表,能够减少线程间竞争并支持动态扩容以应对高并发场景。随后探讨了JDK 8的重大改进:取消段的概念,采用更细粒度的锁控制,并引入`Node`等内部类以及CAS操作,有效解决了哈希冲突并实现了高性能的并发访问。这些设计使得`ConcurrentHashMap`成为构建高效多线程应用的强大工具。
33 2
|
3月前
|
Java Spring
深入解析Spring源码,揭示JDK动态代理的工作原理。
深入解析Spring源码,揭示JDK动态代理的工作原理。
41 0
|
4月前
|
监控 前端开发 安全
JVM工作原理与实战(十四):JDK9及之后的类加载器
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了JDK8及之前的类加载器、JDK9及之后的类加载器等内容。
114 2
|
4月前
|
安全 Java 开发者
jdk1.8 Optional类从入门到实战
Optional 类是 Java 8 引入的一个容器类,用于表示一个值存在或不存在。其在 java.util 包中,主要目的是为了解决 Java 程序中广泛存在的空指针异常(NullPointerException)问题,同时提供了一种更优雅的方式来处理可能为 null 的对象。 在 Java 8 之前,处理 null 值往往依赖于显式的 null 检查,这种方式不仅增加了代码的复杂度,而且容易出错。Optional 类提供了一种更好的解决方案,通过封装可能为 null 的值,强制开发者显式地处理值存在或不存在的情况,从而减少在运行时出现 NullPointerException 的可能性。
43 3
|
4月前
|
设计模式 Java
根据JDK源码Calendar来看工厂模式和建造者模式
根据JDK源码Calendar来看工厂模式和建造者模式