一文搞懂Java并发AQS的共享锁模式

一文搞懂Java并发AQS的共享锁模式

什么是AQS

AQS全称为AbstractQueuedSynchronizer(抽象队列式同步器),是Java并发包中的一种基础组件,用于实现锁和同步器工具类。在Java中,锁和同步器的实现往往都依赖于AQS。

AQS实现了一个双向队列,队列里面的元素是“线程节点”,每一个线程节点都可以对应一个线程。线程节点可以用来保存等待线程相关的状态(如是否被中断、是否已经获取到锁等),并且可以被高效的挂起/恢复。AQS中定义了一些方法,可以让我们在实现锁和同步器时方便的使用和扩展这个队列。

什么是共享锁

在并发编程中,共享锁是一种允许多个线程同时读取同一个资源的锁。在一个线程持有共享锁时,其他线程也可以获得该锁,从而多个线程同时访问同一个资源。

共享锁和排它锁是相对的,排它锁只允许一个线程访问资源,其他线程需要等待当前线程释放锁之后才能访问。

为什么需要共享锁

共享锁可以提高系统吞吐量,因为多个线程可以同时读取同一份资源,避免单线程瓶颈。

同时,共享锁的使用也有助于避免数据不一致问题。在并发编程中,如果多个线程同时写入同一份资源,可能导致数据不一致,而共享锁仅允许多个线程同时读取同一个资源,避免了这个问题,是一种非常重要的多线程编程技术。

AQS中的共享锁

AQS中实现了独占锁和共享锁两种锁,其中共享锁有两种模式:共享锁和读锁。

共享锁

共享锁是一种允许多个线程同时访问同一个资源的锁。共享锁可以使用AQS中的tryAcquireShared方法尝试获取锁,使用tryReleaseShared方法释放锁。这些方法在实现上非常类似于独占锁中的tryAcquire和tryRelease方法。不过,共享锁允许多个线程同时获取锁,因此在实现时需要考虑多个线程并发访问时的线程安全问题。

AQS中共享锁的实现依赖于一个int类型的state变量。state变量存储了当前锁被持有的次数,其中低16位表示共享锁的数量,高16位表示独占锁的次数。当一个线程尝试获取共享锁时,它会调用AQS中的tryAcquireShared方法,这个方法会判断state变量中低16位的值是否为0。如果是0,那么表示当前没有线程占用共享锁,当前线程可以获取到锁;否则,当前线程需要被加入到等待队列中,等待其他线程释放锁之后再次尝试获取。当线程成功获取共享锁时,state变量的低16位的值会增加1。

当一个线程释放共享锁时,它会调用AQS中的tryReleaseShared方法。这个方法会把当前线程持有的共享锁数量减1,并且唤醒等待队列中的一个线程。如果当前线程持有的共享锁数量减为0,那么表示当前线程不再持有锁,tryReleaseShared方法返回true。

读锁

读锁和共享锁类似,也是允许多个线程同时访问同一个资源的锁。不过,读锁只能用于读操作,不能用于写操作。和共享锁一样,读锁也可以使用AQS中的tryAcquireShared方法尝试获取锁,使用tryReleaseShared方法释放锁。和共享锁不同的是,读锁的获取和释放并不会影响state变量,也就是说,读锁获取和释放的实现不使用state变量,而是使用类似于共享锁的等待队列来实现。

两个示例

示例1:并发读写文件共享锁

本示例中假设有一个文件,多个线程需要并发读写这个文件。因为读操作是安全的,所以可以使用共享锁实现多线程的读操作;而写操作是需要互斥的,因此需要使用独占锁实现多线程的写操作。

示例实现中定义了ReadWriteLock类,通过实现AQS的共享锁和独占锁,封装了对文件的并发读写操作。

public class ReadWriteLock {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final Map<String, byte[]> files = new ConcurrentHashMap<>();

    public byte[] read(String fileName) throws InterruptedException {
        lock.readLock().lockInterruptibly();
        try {
            return files.get(fileName);
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write(String fileName, byte[] data) throws InterruptedException {
        lock.writeLock().lockInterruptibly();
        try {
            files.put(fileName, data);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

在这个示例中,通过ReentrantReadWriteLock类实例化读写锁ReentrantReadWriteLock,并调用lock的readLock和writeLock方法获取读锁和写锁。读写操作分别对应两个公共方法read和write,这些方法使用了AQS中的共享锁和独占锁实现。

示例2:乐观读取并发HashSet

本示例中假设有多个线程需要并发读写一个HashSet。因为读操作是安全的,所以可以使用共享锁实现多线程的读操作;而写操作比较耗时,需要减小独占锁的获取和释放等开销,因此可以使用乐观读取(optimistic reads)来实现多线程的写操作。

示例实现中定义了ConcurrentHashSet类,通过继承AbstractSet类和使用AQS的共享锁和条件变量,实现对HashSet的并发读写操作。

public class ConcurrentHashSet<E> extends AbstractSet<E> {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final Set<E> set = new HashSet<E>();

    @Override
    public Iterator<E> iterator() {
        return set.iterator();
    }

    @Override
    public int size() {
        lock.readLock().lock();
        try {
            return set.size();
        } finally {
            lock.readLock().unlock();
        }
    }

    @Override
    public boolean add(E e) {
        long stamp = lock.writeLock();
        try {
            return set.add(e);
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    @Override
    public boolean remove(Object o) {
        long stamp = lock.writeLock();
        try {
            return set.remove(o);
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

在这个示例中,通过ReentrantReadWriteLock类实例化读写锁ReentrantReadWriteLock,并调用lock的readLock和writeLock方法获取读锁和写锁。通过使用tryOptimisticRead方法不用加锁实现读操作,使用AQS中的tryConvertToWriteLock方法转化读锁为独占写锁实现写操作。

总结

AQS是Java并发包中一种非常实用的同步机制,通过实现队列,给我们提供了很好的锁和同步器实现基础。在AQS中,共享锁是一种非常重要的同步机制,可以方便的实现多个线程同时读取同一个资源的需求。在使用共享锁时,需要注意线程安全问题,保证锁的正确性。在实际项目中,需要根据实际需求综合考虑锁和同步器的实现方式,使用AQS进行实现。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:一文搞懂Java并发AQS的共享锁模式 - Python技术站

(0)
上一篇 2023年5月16日
下一篇 2023年5月16日

相关文章

  • 易语言实现双线程的方法解析

    易语言实现双线程的方法解析 什么是双线程 双线程是指在一个程序中,可以有两个或以上的线程同时运行。在易语言编程中,实现双线程可以大大提高程序的效率。 实现双线程的方法 在易语言中,实现双线程的方法有两种:使用EasyThread库和使用Win32API。 使用EasyThread库 EasyThread库是易语言中自带的一个多线程库,通过它可以实现简单的多线…

    多线程 2023年5月17日
    00
  • Java多线程之中断线程(Interrupt)的使用详解

    Java多线程之中断线程(Interrupt)的使用详解 在Java中,线程采用协作式多任务处理,即线程必须自主地停止自己或按照其他线程的意愿停止。中断线程是线程之间传递信号的一种机制,允许一个线程打断另一个线程的执行。本文将详细讲解Java多线程之中断线程的使用方法。 中断线程的基本原理以及使用方式 中断线程的本质是给目标线程发出一个中断信号,该信号会将目…

    多线程 2023年5月17日
    00
  • Java并发编程示例(十):线程组

    Java并发编程示例(十):线程组 简介 Java提供了一种称为线程组(Thread Group)的机制来方便地管理一批线程,特别是当多个线程彼此之间存在着逻辑上的相关性时。一个线程组可以包含多个线程,也可以包含多个线程组。 线程组的基本操作 创建线程组:可以通过ThreadGroup类的构造方法来创建一个新的线程组。 ThreadGroup threadG…

    多线程 2023年5月16日
    00
  • C++基于reactor的服务器百万并发实现与讲解

    C++基于Reactor的服务器百万并发实现与讲解 简介 该攻略将介绍基于Reactor模式实现高并发服务器的过程。Reactor模式是一种常见的多路复用I/O技术,用于实现高并发环境下的网络服务器。Reactor模式基于IO多路复用,通过事件驱动的方式,将网络I/O事件分发给对应的处理函数,从而实现高效的I/O操作。 本攻略将着重介绍基于C++实现Reac…

    多线程 2023年5月17日
    00
  • Python多线程编程(二):启动线程的两种方法

    文章标题:Python多线程编程(二):启动线程的两种方法 前言 编写多线程程序可以在某种程度上提高程序的并发性和性能,Python提供了多种方式支持多线程编程。本文将着重讲解启动线程的两种方法:继承threading.Thread类和使用函数式API:threading.Thread。 使用继承方式实现启动线程 创建线程方式中,最常见的方法就是继承Thre…

    多线程 2023年5月17日
    00
  • Linux C中多线程与volatile变量

    针对该问题,我为您提供如下完整讲解: Linux C中多线程与volatile变量 一、volatile变量的概念 在C语言中,volatile是一种类型限定符,通常用于修饰容易发生变化、被多线程访问或外部程序访问等的变量。该限定符告诉编译器不要对变量进行优化,每次使用变量都必须从内存中读取该变量的值,而不是从CPU寄存器中读取,保证多线程或外部程序对该变量…

    多线程 2023年5月16日
    00
  • Python多线程正确用法实例解析

    Python多线程正确用法实例解析 Python中的多线程可以提高程序的性能,但是在使用多线程时需要注意一些细节问题,避免出现错误。本篇文章将讲解Python多线程的正确用法,并给出两个示例来说明多线程的应用。 多线程简介 线程是程序执行的最小单元,多线程指的是程序同时执行多个线程来完成任务,可以提高程序执行效率。Python中的_thread模块和thre…

    多线程 2023年5月17日
    00
  • SpringBoot多线程进行异步请求的处理方式

    让我们来详细讲解一下Spring Boot多线程进行异步请求处理的完整攻略。 什么是异步请求 异步请求是指客户端通过发送请求到服务器端,在等待服务器响应的过程中不会阻塞当前进程的执行,同时也不会阻塞其他的程序执行或用户操作。 相比于传统的同步请求,异步请求的主要优点是提高了应用程序的性能和并行处理能力。 Spring Boot多线程处理异步请求的方式 在Sp…

    多线程 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部