并行化资源池队列 2 —— 无锁化的无界队列

本文介绍Java 5.0引入的非阻塞算法,并详细解释了如何利用原子变量实现无锁化的并行队列。文章探讨了非阻塞算法的优势,包括提高吞吐率和避免死锁等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java™ 5.0 第一次让使用 Java 语言开发非阻塞算法成为可能,java.util.concurrent 包充分地利用了这个功能。非阻塞算法属于并发算法,它们可以安全地派生它们的线程,不通过锁定派生,而是通过低级的原子性的硬件原生形式 —— 例如比较和交换。非阻塞算法的设计与实现极为困难,但是它们能够提供更好的吞吐率,对生存问题(例如死锁和优先级反转)也能提供更好的防御。

在不只一个线程访问一个互斥的变量时,所有线程都必须使用同步,否则就可能会发生一些非常糟糕的事情。Java 语言中主要的同步手段就是 synchronized 关键字(也称为内置锁),它强制实行互斥,确保执行synchronized 块的线程的动作,能够被后来执行受相同锁保护的 synchronized 块的其他线程看到。在使用得当的时候,内置锁可以让程序做到线程安全,但是在使用锁定保护短的代码路径,而且线程频繁地争用锁的时候,锁定可能成为相当繁重的操作。

原子变量提供了原子性的读-写-修改操作,可以在不使用锁的情况下安全地更新共享变量。原子变量的内存语义与volatile 变量类似,但是因为它们也可以被原子性地修改,所以可以把它们用作不使用锁的并发算法的基础。基于这些原子化操作构建起来的并发控制算法称为无锁化非阻塞算法,所谓无锁化其实并不是不加锁,只是所加的锁粒度极小(指令级别或微指令级别),因此从程序本身的宏观角度来看,就好象不使用锁进行并发控制一样,正因为如此无锁化操作,需要在CPU指令级别提供基础支持,当前较新的CPU芯片都提供了原子化的CMPXHG指令,来实现原子化操作的支持。在Java平台上所提供的原子化操作API,如果宿主系统提供原子化指令,那么Java的原子化操作就会使用原子化指令实现原子化操作,反之Java的原子化操作会采用细粒度自旋锁来实现,当然所有这些对于使用者都是透明化的。如果深入 JVM 和操作系统,会发现非阻塞算法无处不在。如垃圾收集器使用非阻塞算法加快并发和平行的垃圾搜集;调度器使用非阻塞算法有效地调度线程和进程等。非阻塞算法要比基于锁的算法复杂得多。开发非阻塞算法是相当专业的训练,而且要证明算法的正确也极为困难。但是在 Java 版本之间并发性能上的众多改进来自对非阻塞算法的采用,而且随着并发性能变得越来越重要,可以预见在 Java 平台的未来发行版中,会使用更多的非阻塞算法。

采用非阻塞思想来实现无锁化并行队列,其最难理解的核心思想部分是“通过让较快的线程帮助较慢的线程来防止方法调用陷入饥饿”,其次是要一直意识到非阻塞算法并不是不使用锁,而是使用粒度极小的原子锁。因此与前面的算法实现一样,首先要构建队列节点元素,但节点的next域采用原子化引用AtomicReference来直向下一个元素节点,因此队列本身是一个包含哨兵头结点和尾节点,以及由AtomicReference串接起来的队列,同时每个元素节点还包含各自的节点值,这里每个节点值采用Java泛型化类型表示。队列节点定义代码如下:

import java.util.concurrent.atomic.AtomicReference;  
   
public class NoLockNode<E> {  
   
   final E value;  
   final AtomicReference<NoLockNode<E>> next;  
   public NoLockNode(E item,NoLockNode<E> next){  
	   this.value=item;  
	   this.next=new AtomicReference<NoLockNode<E>>(next);  
   }  
   
}  

然后定义队列的主体实现,其中主要包括:入队和出对操作。

import java.util.concurrent.atomic.AtomicReference;
public class NoLockQueue<E> {
 
  //头尾哨兵节点
  private AtomicReference<NoLockNode<E>>head,tail=null;
  //构造函数初始化哨兵节点,让头尾指针相等,进而构造空队列
  public NoLockQueue(){
 
      head=new AtomicReference<NoLockNode<E>>(new NoLockNode<E>(null,null));
      tail=head;
  }
 
//入队实现方法
  publicvoidenq(E value){
//创建将要入队的新节点
      NoLockNode<E> node=new NoLockNode<E>(value,null);
      while(true){
//通过尾节点获取链表最后一个元素节点,以及该元素节点的后继结点。因为入//队操作要从队尾进行
         NoLockNode<E> last=tail.get();
         NoLockNode<E> next=last.next.get();
		 
//判断最后一个节点是否为尾节点,此判断通过只说明最后一个节点为“疑似尾节点”,
//并不能最后定论,因为线程在运行过程中并没有加锁控制,
//是无锁化//运行的,因此在任何时刻都可能改变上一时刻的状态
         if(last==tail.get()){
		 
//所以还要增加判断疑似尾节点的后继节点,以便验明正身其确为尾节点。
//如果疑似尾节点的后继节点不为空,那说明有后继结点,
//说明上一个判断的状态被打破
//(不管是如何被打破的,因为在并发情况下原因会很多),
//所以停止本次尝试开始新一次尝试;如果疑似尾节点的后继节点为空,
//那说明没有后继结点,此时尝试通过原子化的CAS操作添加新节点

            if(next==null){
			
//调用CAS操作在最后一个节点的后继上添加新节点,如果添加失败,
//说明被其//他线程抢先添加了新节点,所以停止本次尝试并开始一次新的尝试

               if(last.next.compareAndSet(next,node)){
			   
//如果成功添加了新的节点,线程要第二次调用CAS操作,
//以便使tail指向新节//点。这里是“帮助”思想的体现,这个调用可能会失败,不过无所谓,
//因为即//便失败线程也能成功返回,因为这个调用只有在其他某个线程已经设置了
//tail节点来“帮助”了本线程时才可能失败

                   tail.compareAndSet(last,node);
                   return;
               }
            }
         }
 
//如果最后一个元素节点还有后继节点,那么要方法要在插入新节点之前,
//调整tail指向最后一个节点的后继节点,这就是“帮助”思想的一个体现。
//这里通过调整tail,来尝试帮助其他线程。因为此时其他线程可能刚刚插入新节点,
//同时还没有来得及调整tail,因此当前这个入队线程检测到这种情况发生后,
//要主动尝试协助已经成功添加入队元素的线程调整tail。
//因为采用原子化操作调整tail因此无需等待,所以该入队方法在这一点上实现了线性化。
//(这是原子化操作其实是粒度极小的指令级别锁的体现)

          else{
            tail.compareAndSet(last,next);
         }
      }
  }
 
//出队方法
  public E deq()throws Exception{
 
      E result=null;
      while(true){
//通过头节点获取链表第一个元素节点,以及该元素节点的后继结点。
//因为出队操作要从队首进行,同时还要通过队尾节点获取最后一个节点,用于进行相关的判断
         NoLockNode<E> first=head.get();
         NoLockNode<E> last=tail.get();
         NoLockNode<E> next=first.next.get();
 
//判断首节点是否为头节点
         if(first==head.get()){
 
//判断首节点是否指向最后一个节点,如果是则队列可能为空
            if(first==last){
 
//如果head等于tail,同时head后继为空,说明队列为空,那么抛出相关异常,//结束操作
               if(next==null){
 
                   throw new Exception("Empty Queue!!!!");
               }
 
//如果head等于tail,同时head后继不为空,说明队列为非空,同时说明tail
//的调整滞后了,因为此时可能由于并发的其他线程成功的入队后,但还没有调整tail,
//这时出对线程开始了运行,所以同样需要出队线程来“帮助”调整//tail节点
               tail.compareAndSet(last,next);
            }
         }
 
//如果首节点没有指向头节点,说明头节点说明头节点已经被其他线程改变,
//那么要取出首节点的节点值,同时尝试设置头节点指向首节点的后继节点,
//如果设置成功,则返回节点值,即出队成功
          else{
//取出首节点值
            result=next.value;
//尝试设置头节点指向首节点的后继节点,如果设置成功,则返回节点值,即出队成功
            if(head.compareAndSet(first,next))
               return result;
         }
      }
  } 
}



很容易看出这种实现的队列是无锁的。每个方法调用首先找出一个未完成的入队操作,然后尝试完成它。最坏的情形下,所有的线程都试图移动队列的tail域,且其中之一必须成功。仅当另一个线程的方法调用在改变引用中获得成功时,一个线程才可能在入队或出队一个节点时失败,因此总会有某个方法调用成功。这种无锁化的实现从本质上改进了队列的性能,无锁算法的性能比最有效的阻塞算法还要高。

Java并发包(java.util.concurrent)常用类及使用场景 1、locks包中的(如ReentrantLock等),提供更灵活的机制,替代 synchronized。 ● ReentrantLock 可重入,支持公平和非公平,可中断、可限时等待。 ● ReentrantReadWriteLock 读写分离,允许多个读线程同时访问,写线程独占资源。 ● StampedLock (Java 8+)支持乐观读,适用于读多写少场景,性能优于 ReentrantReadWriteLock。 ● Condition 替代 Object.wait()/notify(),实现更精细的线程等待/唤醒机制。 2、atomic包下的原子类:如AtomicInteger、AtomicLong、LongAdder等,原子类提供无线程安全操作,基于CAS(Compare and Swap)实现,适用于计数器、状态标志等场景。 3、提供了线程安全的集合类,支持高并发读写。 ● ConcurrentHashMap:使用节点的思想,即采用“CAS+synchronized”的机制来保证线程安全。 ● CopyOnWriteArrayList/CopyOnWriteArraySet:核心机制是写时复制(Copy-On-Write,简称 COW机制),读操作无,写操作复制新数组,适合读多写少的场景。 ● BlockingQueue(接口):实现类有ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue。都是阻塞式队列,支持生产者-消费者模型。 ● ArrayBlockingQueue:基于数组的有界队列。 ● LinkedBlockingQueue:基于链表的无界队列(默认最大为 Integer.MAX_VALUE)。 ● PriorityBlockingQueue:支持优先级的无界队列。 ● SynchronousQueue:无缓冲队列,直接传递任务给消费者。 4、Executor线程池框架,管理线程的生命周期和任务调度。 ExecutorService(接口),实现有ThreadPoolExecutor、ScheduledThreadPoolExecutor,用于执行异步任务,支持定时/延迟任务。 Future/FutureTask,用于获取异步任务执行结果。 5、Executors线程池工具类,内部通过调用ThreadPoolExecutor构造函数来实现,通过该类提供的静态方法可以快速创建一些常用线程池。 ThreadPoolExecutor类是线程池类,可以通过ThreadPoolExecutor类手动创建线程池。 6、Fork/Join框架:是jdk7中新增的并行执行任务的框架,通过 递归任务拆分 和 工作窃取算法,充分利用多核处理器的计算能力。 ForkJoinPool是执行Fork/Join任务的线程池;ForkJoinTask是要执行的任务的基类,常用的有RecursiveAction(无返回值的任务)和RecursiveTask(有返回值的任务)。 7、CompletableFuture实现对多线程进行编排,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简异步编程的复杂性,提供了函数式编程的能力。 8、AQS(AbstractQueuedSynchronizer抽象排队同步器)是用于构建和同步器的核心框架。几乎所有 Java 并发工具类(如 ReentrantLock、Semaphore、CountDownLatch 、CyclicBarrier 等)的底层实现都依赖于 AQS。它通过一个双向队列(CLH 变体)和状态变量,实现了线程的排队、阻塞与唤醒机制。 9、同步工具类(如Semaphore信号量、CountDownLatch(倒计时门闩)、CyclicBarrier循环屏障、Phaser(阶段器)、Exchanger(交换器)),协调多线程的执行顺序或状态。 ● Semaphore(信号量),用于控制资源访问并发数,可以用在限流等场景。 ● CountDownLatch(倒计时门闩)让主线程等待一组子线程完成任务(一次性)。用于并行任务完成后汇总结果(如多线程加载数据后启动系统)。 ● CyclicBarrier(循环屏障),用于一组线程相互等待,到达屏障后统一执行后续操作(可重复使用),例如分阶段数据处理后合并。 ● Phaser(阶段器):与CyclicBarrier类似,也是一种多线程同步工具,但是支持更灵活的栅栏操作,可以动态地注册和注销参与者,并可以控制各个参与者的到达和离开(复杂版CyclicBarrier)。 ● Exchanger(交换器):两个线程在特定点交换数据,例如生产者-消费者模式中的缓冲区交换。 生成思维导图
05-10
<think>好的,我现在需要帮用户生成关于Java并发包(java.util.concurrent)常用类及其使用场景的思维导图。首先,我得回顾用户之前的提问,他之前询问了线程池的创建方式,现在转向并发包的其他类,所以需要确保覆盖线程池之外的内容。 用户提供的引用[1]和[2]提到了java.util.concurrent包的整体介绍和ConcurrentHashMap,但用户现在需要更全面的思维导图,包括其他常用类。我需要考虑并发包的主要组成部分,比如线程池、集合类、同步器、原子类、、Future等。 首先,线程池部分用户之前已经详细讨论过,所以这里可能需要简要提及,但重点放在其他类。接下来是并发集合类,比如ConcurrentHashMap、CopyOnWriteArrayList,这些在引用[2]中提到过,需要详细说明它们的使用场景。 然后是同步器,如CountDownLatch、CyclicBarrier、Semaphore、Exchanger,这些都是常用的工具,用于多线程间的协调。原子类比如AtomicInteger,AtomicReference,这些提供无的线程安全操作,适用于计数器等场景。 机制方面,ReentrantLock和StampedLock是比synchronized更灵活的,需要解释它们的区别和使用场景。Future和CompletableFuture用于异步编程,处理异步任务的结果。 此外,可能还需要包括Fork/Join框架,虽然用户之前提到过,但作为并发包的一部分,应该包含在思维导图中。最后,其他工具类如ThreadLocalRandom,用于生成随机数,适合多线程环境。 需要确保每个类都有简洁的使用场景说明,例如ConcurrentHashMap适合高并发读写,CopyOnWriteArrayList适合读多写少的场景。同步器部分要区分CountDownLatch和CyclicBarrier的不同用途,比如前者是一次性的,后者可重复使用。 还要考虑用户可能的应用场景,比如高并发数据处理、任务分解、资源池管理等,将这些场景与对应的类关联起来。结构上需要层次分明,大类下分子类,每个子类附上使用场景和示例代码片段。 需要避免过于技术的术语,保持解释清晰易懂,同时确保覆盖所有重要类。最后检查是否有遗漏的重要类,比如BlockingQueue的不同实现,如ArrayBlockingQueue和LinkedBlockingQueue,它们在线程池中常用,但作为并发集合的一部分也应包括在内。 总结下来,思维导图的大纲应该是:线程池、并发集合、同步器、原子类、、Future/ForkJoin、其他工具类,每个部分下列举具体类及其使用场景,并给出简要代码示例。这样用户能够一目了然地理解各个类的作用和适用情况。</think>根据Java并发包的体系结构,我为您整理以下思维导图框架及核心内容。以下采用层级分类+场景说明的形式呈现,便于理解记忆: --- ### **Java并发包(java.util.concurrent)核心类思维导图** ```mermaid graph TD A[java.util.concurrent] --> B[线程池] A --> C[并发集合] A --> D[同步器] A --> E[原子类] A --> F[机制] A --> G[Future体系] A --> H[Fork/Join] A --> I[其他工具] B --> B1(ThreadPoolExecutor) B --> B2(ScheduledThreadPoolExecutor) B --> B3(Executors工厂类) C --> C1(ConcurrentHashMap) C --> C2(CopyOnWriteArrayList) C --> C3(BlockingQueue体系) D --> D1(CountDownLatch) D --> D2(CyclicBarrier) D --> D3(Semaphore) D --> D4(Exchanger) E --> E1(AtomicInteger) E --> E2(AtomicReference) E --> E3(LongAdder) F --> F1(ReentrantLock) F --> F2(StampedLock) G --> G1(FutureTask) G --> G2(CompletableFuture) H --> H1(ForkJoinPool) H --> H2(RecursiveTask) I --> I1(ThreadLocalRandom) I --> I2(TimeUnit) ``` --- ### **核心类使用场景详解** #### **1. 线程池(ThreadPoolExecutor)** - **典型场景**:Web服务器请求处理、批量数据处理 - **示例配置**: ```java new ThreadPoolExecutor( 5, // 核心线程数=服务器CPU核心数 20, // 最大线程数=核心数*4 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new NamedThreadFactory("Order-Processor") // 自定义线程命名 ) ``` #### **2. 并发集合** - **ConcurrentHashMap** - 场景:高频读写的缓存系统(如电商商品库存管理) - 特性:分段技术,默认16个段[^2] - **CopyOnWriteArrayList** - 场景:读多写少的监听器列表(如Spring事件监听器) - 原理:写操作时复制新数组 - **BlockingQueue** ```java // 生产者-消费者模式实现 BlockingQueue<Order> queue = new LinkedBlockingQueue<>(1000); // 生产者 queue.put(order); // 消费者 Order order = queue.take(); ``` #### **3. 同步器** - **CountDownLatch** - 场景:主线程等待多个子任务初始完成 ```java CountDownLatch latch = new CountDownLatch(3); // 子任务完成时调用 latch.countDown() latch.await(); // 主线程阻塞等待 ``` - **CyclicBarrier** - 场景:多阶段并行计算(如MapReduce分阶段处理) ```java CyclicBarrier barrier = new CyclicBarrier(5, () -> System.out.println("所有线程到达屏障")); ``` - **Semaphore** - 场景:数据库连接池限流 ```java Semaphore semaphore = new Semaphore(10); // 允许10个并发 semaphore.acquire(); // 获取许可 // 访问资源... semaphore.release(); ``` #### **4. 原子类** - **AtomicInteger** - 场景:无计数器(如网站访问量统计) ```java AtomicInteger counter = new AtomicInteger(0); counter.incrementAndGet(); // 原子自增 ``` - **LongAdder** - 场景:高并发统计(比AtomicLong性能更高) #### **5. 机制** - **ReentrantLock** - 场景:细粒度控制(如转账操作的双重校验) ```java lock.lock(); try { if (balance >= amount) { balance -= amount; } } finally { lock.unlock(); } ``` - **StampedLock** - 场景:读多写少的乐观控制(比ReentrantReadWriteLock性能更高) #### **6. Future体系** - **CompletableFuture** - 场景:异步编排多个服务调用 ```java CompletableFuture.supplyAsync(() -> queryUser(id)) .thenApplyAsync(user -> calcScore(user)) .thenAccept(score -> saveScore(score)); ``` --- ### **典型应用场景矩阵** | 并发需求 | 推荐类/接口 | 性能特点 | |------------------------|---------------------------|----------------------------| | 资源池管理 | ThreadPoolExecutor | 可控的线程生命周期管理 | | 高并发缓存 | ConcurrentHashMap | 分段提升并发读写效率[^2] | | 任务编排 | CompletableFuture | 函数式编程+异步流水线 | | 分布式计算同步 | CyclicBarrier | 可重复使用的阶段同步屏障 | | 无计数器 | LongAdder | 分散热点数据降低竞争 | --- ### **补充说明** 1. **的选择原则**: - 优先使用`synchronized`(JVM优成熟) - 需要超时/中断功能时选择`ReentrantLock` - 读多写少场景使用`StampedLock` 2. **并发集合的选型公式**: $$ \text{选择依据} = \text{读写比例} + \text{数据一致性要求} + \text{内存约束} $$ --- ### **相关问题** 1. ConcurrentHashMap在JDK8中如何实现线程安全? 2. 如何避免CompletableFuture的回调地狱问题? 3. LongAdder相比AtomicLong有哪些性能优
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值