JUC最全详解(万字图文)

JUC最全详解(万字图文)-mikechen

JUC,是Java Util Concurrency的缩写,是 Java 并发编程工具包,下面我重点详解JUC@mikechen

什么是JUC?

JUC,是Java Util Concurrency的缩写,是 Java 编程语言中用于处理并发编程的工具和框架的集合。

JUC 提供了一组类和工具,用于帮助 Java 开发者更容易地编写多线程和并发应用程序,以确保线程安全和高性能。

 

JUC组件

JUC 包括许多重要的并发编程组件,包含了:线程池、并发锁、并发工具类、异步编程等等,下面分别重点详解@mikechen

线程池

允许管理和重用线程,减少线程的创建和销毁开销,以及控制并发线程的数量。

线程池的实现

Java 提供了一个名为 ExecutorService 的接口,它表示线程池,并有多种实现类。

如下图所示:

JUC最全详解(万字图文)-mikechen

Java提供的7种线程池实现:

1)Executors.newFixedThreadPool

创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

2)Executors.newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

3)Executors.newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

4)Executors.newScheduledThreadPool

创建一个可以执行延迟任务的线程池。

5)Executors.newSingleThreadScheduledExecutor

创建一个单线程的可以执行延迟任务的线程池。

6)Executors.newWorkStealingPool

创建一个抢占式执行的线程池(任务执行顺序不确定),这个是JDK 1.8 添加。

7)ThreadPoolExecutor

手动创建线程池的方式,它创建时最多可以设置 7 个参数。

线程池的参数

线程池的参数,如下图所示:

JUC最全详解(万字图文)-mikechen

  • 核心线程数:线程池保持的最小线程数,即使它们是空闲的。
  • 最大线程数:线程池能够同时运行的最大线程数。
  • 队列:用于存储等待执行的任务的队列,可以是有界队列或无界队列。
  • 空闲线程的存活时间:当线程池中的线程多于核心线程数时,空闲线程的最大存活时间。
  • 拒绝策略:当队列已满且线程池中的线程数达到最大线程数时,定义如何处理新的任务。常见的策略包括抛出异常、阻塞等待、丢弃任务等。

线程池的原理

ThreadPool线程池的工作原理,如下图所示:

JUC最全详解(万字图文)-mikechen

1.检查线程池是否已满

如果线程池中的线程数小于 corePoolSize,则创建新线程来执行任务。

2.将任务放入队列

如果线程池中的线程数大于等于 corePoolSize,则将任务放入任务队列,直到有空闲的线程可用。

3.准备开始处理任务

当一个线程完成了任务的执行后,它会返回线程池,并且准备处理下一个任务。

4.如果已满执行拒绝策略

如果线程池中的线程数量已经达到了限制,而且任务队列已经满了,那么线程池会拒绝该任务并执行拒绝策略。

 

Locks(锁机制)

提供更灵活的锁机制,如Synchronized和 ReentrantLock,用于精细控制线程的互斥访问。

Synchronized是解决多个线程之间访问资源的同步性,Synchronized 翻译为中文的意思是同步,也称之为”同步锁“。

当一个线程尝试进入一个 Synchronized 代码块时,JVM 会在对象头中设置一个标记位来表示该对象已经被锁定,并把这个锁放入到线程的锁池中。

Java对象头主要包括两部分数据,如下图所示:

JUC最全详解(万字图文)-mikechen

主要包含类型指针和标记字段:

1)类型指针(Klass Pointer)

是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

2)标记字段(Mark Word)

用于存储对象自身的运行时数据,比如:GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID等等。

ReentrantLock

ReentrantLock是Java中的一个可重入锁,是一种比synchronized更加灵活和强大的同步机制。

ReentrantLock是基于AQS来实现,AQS全名:AbstractQueuedSynchronize。

AQS它实现了一个FIFO的队列,底层实现的数据结构是一个双向链表,如下图所示:

JUC最全详解(万字图文)-mikechen

AQS维护了一个状态变量state,用于表示锁的状态,以及一个线程变量exclusiveOwnerThread,用于记录当前持有锁的线程。

 

并发集合

线程安全的集合,如 ConcurrentHashMapConcurrentLinkedQueue,用于在多线程环境中安全地存储和访问数据。

ConcurrentHashMap是Java集合框架中的一个线程安全的哈希表实现,它支持高效地并发访问和修改操作。

在JDK1.7中ConcurrentHashMap采用了:数组+Segment+分段锁的方式实现。

如下图所示:
JUC最全详解(万字图文)-mikechen

在JDK 1.8中,ConcurrentHashMap的实现经过了重大的改进,如下图所示:

JUC最全详解(万字图文)-mikechen

参考了JDK8 HashMap的实现,进行全面升级。

JDK 1.8中的ConcurrentHashMap没有了“段”的概念,而是直接使用数组来存储Node,每个Node表示一个键值对。

 

ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个非阻塞、线程安全的队列实现,它可以用于多个线程之间的并发队列操作。

该队列的操作包括添加元素、移除元素等,都是线程安全的,没有需要显式加锁。

 

CopyOnWriteArrayList

CopyOnWriteArrayList是一个并发列表,适用于读多写少的场景。它通过复制底层数组来实现线程安全。

当有写入操作时,会复制底层数组并进行写入,而读取操作则不需要锁定。

这些并发集合提供了多种数据结构,用于不同的并发场景。

它们可以帮助开发者编写高性能、线程安全的多线程应用程序,同时避免了手动加锁和同步的复杂性。

 

原子变量

用于执行原子操作,如 AtomicIntegerAtomicLong,确保多线程环境下的数据一致性。

AtomicInteger 是 Java 中的一个原子整数类,它属于 Java Util Concurrency(JUC)包。

AtomicInteger提供了一系列原子性操作,包括增加、减少、获取、设置等,这些操作可以确保在多线程环境下对整数值的操作是原子的。

如下所示:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger(0);
        
        int currentValue = counter.get();
        System.out.println("Current Value: " + currentValue);
        
        int newValue = counter.incrementAndGet();
        System.out.println("New Value: " + newValue);
        
        int updatedValue = counter.addAndGet(5);
        System.out.println("Updated Value: " + updatedValue);
    }
}

AtomicInteger内部使用了 CAS(Compare-And-Swap)操作,这是一种硬件级别的原子性操作,用于保证线程安全。

它不需要显式的同步(如锁),因此在多线程环境中表现出色良好的性能。

 

并发工具类

用于协调多个线程之间的操作,如 CountDownLatchCyclicBarrierSemaphore

如下图所示:

JUC最全详解(万字图文)-mikechen

提供了比synchronized更加高级的各种同步结构:包括CountDownLatchCyclicBarrierSemaphore等,可以实现更加丰富的多线程操作。

  • CountDownLatch是一个同步的辅助类,允许一个或多个线程,等待其他一组线程完成操作,再继续执行。
  • CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier),和CountDownLatch相似,也是等待某些线程都做完以后再执行。
  • Semaphore可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。
  • Exchanger(交换者)是一个用于线程间协作的工具类,Exchanger用于进行线程间的数据交换。

 

异步编程

Future 和 CompletableFuture:用于支持异步编程和获取异步操作的结果。

1.Future 

Future 是 Java 中的一个接口,它代表一个尚未完成的异步任务,允许您查询任务的状态和获取结果。

通过 submit 方法,可以将任务提交给一个 Executor,并获得一个 Future 对象,以便稍后获取任务的结果。

2.CompletableFuture 

CompletableFuture 是 Java 8 引入的类,它是 Future 的扩展,提供更多功能,使异步编程更容易。

它允许您以非阻塞的方式组合多个异步任务、处理异常、设置回调函数等。

如下所示:

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 42);
future.thenApply(result -> result * 2)
      .thenAccept(finalResult -> System.out.println("Result: " + finalResult));

CompletableFuture 是 Java 中现代异步编程的首选方式,是更强大的异步编程工具。

CompletableFuture 提供了:丰富的功能来处理异步任务、组合操作、异常处理和回调函数,使得异步编程更加方便和灵活。

 

JUC总结

JUC 的目标是提供强大的工具和框架,以帮助 Java 开发者编写高效、线程安全的多线程应用程序。

同时避免常见的并发问题,如竞态条件、死锁和数据竞争。

通过 JUC,开发者可以更容易地实现多线程应用,提高应用程序的性能和可伸缩性。

评论交流
    说说你的看法