java-JUC概述(进行分类总结-包含原子类、并发集合、线程等)
文章目录
目录
文章目录
前言
一、原子类(Atomic Classes)
1. 基本类型原子类
2. 引用类型原子类
3. 数组原子类
4. 字段更新器
5.问题
1.其他基本类型为什么不在原子类中
1.CAS指令的底层支持
2.高频使用场景优先
3.替代方案与扩展能力
二、并发集合(Concurrent Collections)
1.ConcurrentHashMap
2.CopyOnWriteArrayList
3.BlockingQueue 接口
三、锁
1.Lock 接口
2.ReadWriteLock 接口
3.Condition 接口
4.AbstractQueuedSynchronizer (AQS)
四、同步工具类(Synchronizers)
1.CountDownLatch
2.Semaphore
3.CyclicBarrier
4.Exchanger
5.Phaser(Java 7+)
总结
五、线程池
1.Executor 接口
2.ExecutorService 接口(extends Executor)
3.Executors 工厂类
六、异步结果(Future)
1.Future 接口
2.CompletableFuture(JDK1.8)
七、分治框架(Fork/Join)
1.ForkJoinPool
2.RecursiveTask(返回结果) / RecursiveAction(无结果)
总结
前言
java.util.concurrent
(JUC)包是 Java 并发编程的核心工具包,提供了丰富的多线程编程工具类、接口和框架
下面从该包下常用的类和接口进行总结
一、原子类(Atomic Classes)
Java原子类(Atomic Classes)是java.util.concurrent.atomic
包中提供的一组工具类,用于在多线程环境下实现无锁的线程安全操作。其核心机制基于CAS(Compare-And-Swap)硬件指令,通过硬件级原子操作保证变量修改的不可分割性
适用于计数器、状态标志等简单原子操作场景。
为什么需要原子类:
在线程中使用普通的Integer、Long也是可以的,只有我们进行复合操作(i++、i=i+1)这样的是线程不安全的,其他安全情况是可以使用普通基本类型的,也就是说原子类已经帮我们优化了i++(getAndIncrement)、i++(compareAndSet)
不使用原子类可以吗?--可以
那么就需要自行加锁(lock/synchronized ),这样的话开销就大了,或者手动实现CAS
1. 基本类型原子类
-
AtomicInteger
/AtomicLong
/AtomicBoolean
-
作用:提供原子操作的整型、长整型和布尔值。
-
核心方法:
incrementAndGet()
,compareAndSet()
。
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子自增
count.compareAndSet(0, 1); // CAS 更新
2. 引用类型原子类
-
AtomicReference
/AtomicStampedReference
(解决 ABA 问题) -
作用:原子更新对象引用。
AtomicReference<String> ref = new AtomicReference<>("init");
ref.compareAndSet("init", "new"); // CAS 更新引用
3. 数组原子类
-
AtomicIntegerArray
/AtomicLongArray
/AtomicReferenceArray
-
作用:原子更新数组中的元素。
int[] arr = {1, 2, 3};
AtomicIntegerArray atomicArray = new AtomicIntegerArray(arr);
atomicArray.getAndIncrement(0); // 原子更新索引0处的值
4. 字段更新器
-
AtomicIntegerFieldUpdater
/AtomicLongFieldUpdater
/AtomicReferenceFieldUpdater
-
作用:原子更新对象的字段(需
volatile
修饰)。
5.问题
1.其他基本类型为什么不在原子类中
这里大家发现了Integer,Long,Boolean这个基本类型都有原子类,为什么其他的没有呢
核心原则:原子类的设计以高频需求和硬件支持为导向,通过通用性设计(如AtomicReference
)覆盖低频场景,避免冗余实现。对于未覆盖的类型,开发者可结合现有工具灵活扩展。
1.CAS指令的底层支持
- 整数类型的天然适配:CAS(Compare-And-Swap)是硬件级别的原子操作,通常直接支持32位(
int
)和64位(long
)整数的原子性操作。 - 浮点数的复杂性:
double
是64位浮点数,其位操作需考虑精度问题,且硬件对浮点数的CAS支持较弱(部分架构甚至不支持
2.高频使用场景优先
int
和long
是编程中最常用的数值类型(如计数器、ID生成),boolean
用于标志位,需求覆盖率高。short
、byte
等类型因使用场景较少(如特定协议解析),未被纳入核心原子类。
3.替代方案与扩展能力
对于非原生原子类型(如Double
、Float
),可通过AtomicReference
包装对象引用实现原子性
AtomicReference<Double> atomicDouble = new AtomicReference<>(0.0);
atomicDouble.compareAndSet(0.0, 1.0); // 原子更新
二、并发集合(Concurrent Collections)
在多线程环境下,传统的集合类(如 ArrayList
、HashMap
)是非线程安全的,可能导致数据不一致或并发修改异常。
并发集合提供线程安全的替代方案,通过优化锁机制(如分段锁、CAS)或数据拷贝策略,实现高并发下的高性能读写。
下面介绍常用的并发集合
1.ConcurrentHashMap
普通的HashMap我们都知道里面没加锁,可以想一下多个线程同时对一个实例对象进行插入同一个值的时候(put(k,v)),k一样,多个线程修改表中的位置是一样的,就可能出现最终值不一致
-
作用:线程安全的哈希表,代替HashMap
-
特点:
-
Java 7:分段锁(Segment),降低锁粒度,允许多线程同时读写不同段。
-
Java 8+:使用
CAS + synchronized
锁单个桶(Node),进一步优化并发性能。
-
-
适用场景:高并发读写,如缓存、计数器。
-
示例:
简单来说,锁的就是每个链表,对于不同的链表不用上锁,因为产生不了并发问题,没涉及修改用一个变量
2.CopyOnWriteArrayList
- 作用:线程安全的
List
,适用于读多写少的场景(如监听器列表)代替Arraylist/LinkedList。 - 原理:写操作时复制整个底层数组,读操作无锁。
- 缺点:写操作性能差(每次复制数组)而且锁的都是整个代码块,数据量大时慎用。
3.BlockingQueue
接口
-
作用:支持阻塞操作的队列,用于生产者-消费者模型。
-
常用实现类:
-
ArrayBlockingQueue
:基于数组的有界队列,容量固定。 -
LinkedBlockingQueue
:基于链表的队列,默认无界(可设置容量)。 -
PriorityBlockingQueue
:按优先级排序的无界队列。 -
SynchronousQueue
:不存储元素,插入操作必须等待移除操作。
-
三、锁
提供更灵活的锁机制和线程同步工具,比传统的 synchronized
关键字更加灵活,支持可中断锁、超时锁等高级功能。
1.Lock
接口
-
实现类:
ReentrantLock
-
特点:可重入锁,支持公平锁与非公平锁。
-
核心方法:
-
lock.lock(); // 获取锁
lock.tryLock(1, TimeUnit.SECONDS); // 尝试获取锁(支持超时)
lock.unlock(); // 释放锁
2.ReadWriteLock
接口
-
实现类:
ReentrantReadWriteLock
-
特点:读写分离,允许多个读线程同时访问,写线程独占。
-
3.Condition
接口
-
作用:替代
Object.wait()/notify()
,与Lock
配合实现线程间协作。
Condition condition = lock.newCondition();
condition.await(); // 等待
condition.signal(); // 唤醒
4.AbstractQueuedSynchronizer
(AQS)
-
作用:构建锁和同步器的底层框架(如
ReentrantLock
、Semaphore
均基于 AQS)。 -
核心机制:通过
state
变量和 CLH 队列管理线程的阻塞与唤醒。
四、同步工具类(Synchronizers)
协调多个线程的执行顺序或资源访问。
1.CountDownLatch
-
作用:一个或多个线程等待其他线程完成操作。
-
示例:
CountDownLatch latch = new CountDownLatch(3);
latch.countDown(); // 计数器减1
latch.await(); // 等待计数器归零
2.Semaphore
作用:控制同时访问资源的线程数(信号量模型)。
Semaphore semaphore = new Semaphore(5); // 允许5个线程同时访问
semaphore.acquire(); // 获取许可
semaphore.release(); // 释放许可
3.CyclicBarrier
-
作用:一组线程互相等待,达到屏障点后继续执行。
-
特点:可重复使用。
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程完成阶段任务"));for (int i = 0; i < 3; i++) {new Thread(() -> {try {// 阶段 1processData();barrier.await();// 阶段 2analyzeData();barrier.await();} catch (Exception e) {e.printStackTrace();}}).start();
}
4.Exchanger
作用:两个线程间交换数据。
适用场景:两个线程间精确交换数据(如线程间传递计算结果)。线程A生成数据,线程B处理数据,双方通过Exchanger交换数据
Exchanger<String> exchanger = new Exchanger<>();
String data = exchanger.exchange("data1"); // 阻塞直到另一个线程调用exchange
5.Phaser
(Java 7+)
-
作用:分阶段的多线程任务同步器,替代
CyclicBarrier
和CountDownLatch
。 -
适用场景:动态任务分片(如MapReduce任务)、多阶段并行计算。
总结
工具类 | 核心机制 | 线程关系 | 可重用性 | 典型场景 |
---|---|---|---|---|
CountDownLatch | 计数器减至0触发 | 1:N(主线程等待子线程) | 否 | 等待多个任务完成 |
CyclicBarrier | 计数器加至N后触发并重置 | N:N(线程间相互等待) | 是 | 分阶段并行任务同步 |
Semaphore | 许可资源池管理 | 共享资源竞争 | 是 | 限流、资源池控制 |
Phaser | 动态阶段划分与同步 | 动态参与线程 | 是 | 动态任务分片与多阶段计算 |
Exchanger | 双线程数据交换点 | 1:1数据交换 | 否 | 线程间数据传递 |
五、线程池
管理线程的生命周期,避免频繁创建和销毁线程的开销。
1.Executor
接口
核心方法:execute(Runnable command)
2.ExecutorService
接口(extends Executor)
-
扩展功能:提交任务、关闭线程池、获取
Future
对象。 -
实现类:
-
ThreadPoolExecutor
:可配置核心线程数、最大线程数、队列等参数。 -
ScheduledThreadPoolExecutor
:支持定时和周期性任务。
-
3.Executors
工厂类
常用方法有5个:
- 基于ThreadPoolExecutor
- newFixedThreadPool(int nThreads):固定大小线程池。
- newCachedThreadPool():弹性线程池(无界,空闲线程自动回收)
- ScheduledThreadPoolExecutor():创建单一线程池
-
基于ScheduledThreadPoolExecutor
- ScheduledThreadPool(int corePoolSize):定时任务
-
基于ForkJoinPool(JDK1.7)
-
newWorkStealingPool(int parallelism):工作窃取线程池
-
高吞吐量:适合CPU密集型任务,减少线程等待
-
任务窃取机制:空闲线程从其他线程的队列尾部窃取任务。
-
-
六、异步结果(Future)
异步计算结果,支持任务结果的获取和取消。
1.Future
接口
核心方法:
future.get(); // 阻塞获取结果
future.isDone(); // 检查任务是否完成
2.CompletableFuture(JDK1.8)
支持链式异步编程,组合多个异步任务。
CompletableFuture.supplyAsync(() -> "Hello").thenApply(s -> s + " World").thenAccept(System.out::println);
七、分治框架(Fork/Join)
将大任务拆分为小任务并行执行,最后合并结果(类似 MapReduce)。适合分治问题
1.ForkJoinPool
基于工作窃取算法(Work-Stealing),空闲线程从其他队列窃取任务。
2.RecursiveTask(返回结果)
/ RecursiveAction(无结果)
就如名字适用于递归,例如计算斐波那契(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)
class FibonacciTask extends RecursiveTask<Integer> {protected Integer compute() {if (n <= 1) return n;FibonacciTask f1 = new FibonacciTask(n - 1);f1.fork();FibonacciTask f2 = new FibonacciTask(n - 2);return f2.compute() + f1.join();}
}
总结
首先确认一个事情JUC包核心目标是 简化并发编程的复杂度,多线程咱们一般使用手动创建线程,但是每次都要创建和销毁线程,导致高并发的时候浪费和消耗资源,因此JUC包提供了线程池,其中JUC包下的工具类,例如线程协作工具(CountDownLatch,Semaphore),原子类,异步任务等都适用于手动创建线程和线程池,而使用多线程的情况下会出现的线程安全问题,JUC包里面提供了锁(lock包)例如(ReentrantReadWriteLock、ReentrantLock)等,以及并发集合(ConcurrentHashMap )等能帮我们更灵活的解决线程安全问题,但是加锁是会占用资源的(其他线程要等待),因此有些简单情况我们可以使用CAS来代替锁,因此JUC包下的原子类(AtomicInteger)等能帮我们很好解决这个问题,当然我们也可以手动实现(底层就是CAS),解决了线程安全,那么对于多个线程的控制,同步等协调操作,JUC也给出了工具类(CountDownLatch、Exchanger、Phaser)等,但是还有是有个问题现在的线程都需要执行完之后才能获取结果,因此出现了异步任务,可以通过(Future)等接口来异步获取结果,而不用等待。因此现在线程资源,线程安全,线程协作,异步任务都有了实现。
1.优先选择高层抽象:
使用 ConcurrentHashMap 替代手动同步的 HashMap。
使用 ExecutorService 管理线程,而非直接创建 Thread。
2.合理选择同步工具:
一次性等待用 CountDownLatch,循环等待用 CyclicBarrier。
资源限流用 Semaphore,线程间交换数据用 Exchanger。
3.避免锁竞争:
使用原子类(如 AtomicInteger)替代锁。
读写分离场景用 ReentrantReadWriteLock。
4.线程池调优:
根据任务类型选择队列(有界 vs 无界)。
设置合理的拒绝策略(如 AbortPolicy、CallerRunsPolicy)。
5.处理异步任务:
使用 CompletableFuture 简化异步编程。
分治任务用 ForkJoinPool 提升性能。