一文搞懂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日

相关文章

  • java多线程和并发包入门示例

    Java多线程和并发包入门示例是Java编程中的重要内容之一,本文将给出完整的攻略。 多线程基础知识 在理解Java多线程和并发包入门示例之前,有必要先了解一些基础知识。以下是需要掌握的关键概念: 线程:线程是在程序中的一条执行路径,和普通的程序执行是不同的。在Java中,每个线程都有一个独立的堆栈,但共享同一个内存区域。 进程:进程是正在运行中的程序的一个…

    多线程 2023年5月16日
    00
  • 使用java的HttpClient实现多线程并发

    使用Java的HttpClient实现多线程并发,包括以下步骤: 1.导入HttpClient库 使用HttpClient进行请求需要导入相应的库,具体可以使用Maven或直接下载jar包导入。 2.创建HttpClient对象 创建HttpClient对象用于发送请求。可以使用HttpClientBuilder类的build方法创建HttpClient对象…

    多线程 2023年5月16日
    00
  • 关于golang高并发的实现与注意事项说明

    关于golang高并发的实现与注意事项说明 Go语言(Golang)因其高并发性能而备受推崇,这也是Go语言最为突出的核心竞争力之一。在使用Go语言进行高并发开发的过程中,有一些需要注意的问题。本文将会介绍如何在Go语言中高效地实现并发以及注意事项说明。 1. Go并发的基本概念 Go语言的并发是基于goroutine(轻量级线程)和channel(管道)两…

    多线程 2023年5月17日
    00
  • PHP编程中尝试程序并发的几种方式总结

    当程序需要处理大量的并发请求时,一个单线程的程序显然不能满足需求,因此需要进行并发编程。在PHP编程中,以下几种方式可以尝试实现程序并发。 1. 多进程编程 多进程编程是通过在操作系统中创建多个子进程并实现进程间通信,从而实现程序并发的技术。在PHP中,可以使用pcntl_fork()函数创建子进程,并通过信号、管道等方式实现进程间通信,例如: $pid =…

    多线程 2023年5月16日
    00
  • Python并发之多进程的方法实例代码

    关于“Python并发之多进程的方法实例代码”的完整攻略,我可以从以下几个方面进行详细讲解: 1. 什么是多进程? 多进程是一种并发编程的方法,它可以让程序同时执行多个任务。在Python中,可以使用multiprocessing模块来实现多进程编程。每个进程都有自己独立的内存空间,可以并发执行不同的任务,从而提高程序的执行效率。 2. 多进程的方法 使用P…

    多线程 2023年5月16日
    00
  • IOS 创建并发线程的实例详解

    IOS 创建并发线程的实例详解 在 iOS 中,我们可以利用 Grand Central Dispatch(GCD) 来方便地创建并发线程。本篇攻略将给出具体的创建并发线程的方法和相关代码示例。 GCD 简介 Grand Central Dispatch(GCD) 是苹果公司推出的一种多核编程的解决方案,在 MacOSX10.6 后首次被引入,以取代原先的 …

    多线程 2023年5月16日
    00
  • Python多线程同步Lock、RLock、Semaphore、Event实例

    Python多线程同步是指保证多个线程之间的数据安全和执行顺序正确。为了实现这个目标,Python提供了多种同步机制,其中包括Lock、RLock、Semaphore、Event等实例。 Lock Lock是最基础的线程同步实例,它使用二元信号量算法来保持同步。当一个线程获得了Lock的锁时,其他线程就不能再获取这个锁,直到该线程释放这个锁为止。 下面是一个…

    多线程 2023年5月17日
    00
  • Java 浅谈 高并发 处理方案详解

    Java浅谈高并发处理方案详解 前言 随着互联网的发展和用户访问量的逐步增加,高并发逐渐成为互联网开发中的常见问题。而 Java 作为一门流行的编程语言,其处理高并发问题的方案也备受关注。本篇文章将浅谈 Java 高并发处理方案,并且给出两个对高并发处理方案的具体示例。 常用的高并发处理方案 多线程 多线程是 Java 中常用的高并发解决方案。可以通过创建多…

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