关于JSR-133规范(Java内存模型和线程规范)的部分内容解读

前言:

该规范对Java语言规范中的两项规范进行了更改(增强),基于也此促使了JVM对这两项规范内容的实现的修改。

①Volatile关键字语义的增强,在原始的规范字这个语义是不具备有序性的,即允许指令的重排序。而在该规范中,Volatile具备了acquire和release的语义,使这该关键字具备是有序性。

②final修饰的属性,即使没有显示的使用同步,也具备线程安全的不可变性。这个效果也许需要采取一些措施,例如例如在设置fnal字段的构造函数末尾设置存储屏障

PS:对于规范了理解,我们不能将它认为是实现原理,他规定了如果你要实现JVM,当开发者在JVM上运行程序的时候,例如,如果开发者在程序中使用了诸如synchronized、volatile关键字,那么JVM允许关键字所产生的效果,符合JSR规范所描述的效果,至于JVM如何去保证这种效果的实现,并不作限制。

对于规范的基本介绍

线程的行为,特别是当没有正确同步时,可能会令人困惑。本规范描述了用Java编程语言编写的多线程程序的语义;它包括通过读取由多个线程更新的共享内存来查看值的规则。由于该规范类似于不同硬件架构的内存模型,因此这些语义被称为Java内存模型。

这些语义并不描述应该如何执行多线程程序。相反。它们描述了允许多线程程序表现的行为。任何只生成允许行为的执行策略都是可接受的执行策略。

对于规范内容的整体总结

1. Java内存模型(JMM)

JMM描述了Java程序如何在多线程环境中共享内存,以及线程之间的数据一致性问题。它规定了Java程序中读写操作的执行顺序,确保正确的线程间通信。它定义了happens-before规则,提供了关于操作之间的顺序性保证。

2. Happens-before规则

JSR-133的核心概念之一是happens-before,它定义了两个操作之间的顺序关系。如果一个操作“happens-before”另一个操作,那么第一个操作的结果对第二个操作可见。常见的happens-before规则包括:

程序顺序规则:单线程中的每个操作按代码顺序执行。

锁定规则:对一个锁的解锁操作happens-before同一个锁的后续加锁操作。

volatile变量规则:对volatile变量的写入操作happens-before对同一变量的后续读操作。

线程启动规则:线程的启动操作happens-before该线程内的任何操作。

线程终止规则:线程的所有操作happens-before该线程的join操作成功返回。

传递性:如果A happens-before B,且B happens-before C,则A happens-before C。

3. volatile变量

JSR-133对volatile关键字做了重要的改进,保证对volatile变量的读写操作具有可见性,并且这些操作不会被重排序。对于一个线程写入volatile变量后,其他线程能够立即看到更新后的值。

4. 锁(synchronized)的内存语义

该规范明确了synchronized关键字的内存语义。进入同步代码块时,线程会刷新变量值,从主内存加载。退出同步代码块时,线程会将对共享变量的更新写回主内存。这样确保了在加锁和解锁之间的操作对其他线程可见。

5. 指令重排序

JMM允许编译器和处理器在不违反happens-before规则的情况下进行指令重排序,以优化程序执行。JSR-133规范定义了在哪些情况下允许指令重排序以及如何保证线程之间的内存可见性。

6. final字段的内存语义

JSR-133对final字段的内存语义做了增强。通过该规范,保证在对象构造时对final字段的写操作在构造函数完成之后,对其他线程可见,并且不会发生重排序。

7. 锁的释放-获取模型

当一个线程释放锁时,JMM保证对共享变量的修改对之后获取同一锁的其他线程可见。这与volatile的内存语义相似。

8. 非阻塞的原子操作

对于一些常用的原子操作(例如AtomicInteger类中的compareAndSet方法),JSR-133规定了它们在多线程环境下的安全性和顺序性,确保这些操作能够被正确执行。

9. 失效缓存与刷新内存

JSR-133描述了线程如何与主存进行交互。对于共享变量,线程会从主内存读取最新的值,并在修改后将值写回主内存。线程的本地缓存可能会暂时保存变量的副本,但通过锁或volatile,能强制刷新这些副本,使其他线程可见最新的更新。

关于JSR-166: Concurrency Utilities (并发工具类)的部分内容解读

前言

JSR-166 是 Java 并发工具类的规范,它引入了 java.util.concurrent 包,为 Java 开发者提供了一套丰富的并发编程工具。该规范首次在 JDK 5 中引入,并在后续版本中得到了扩展和改进。JSR-166 大大简化了多线程编程的复杂性,增强了 Java 的并发处理能力,确保了高效、安全的多线程程序开发。

核心内容

1. 线程池 (Thread Pools)

线程池允许开发者管理线程的创建和重用,减少了频繁创建和销毁线程的开销。

主要类:Executor, ExecutorService, ScheduledExecutorService

常用实现:

ThreadPoolExecutor: 提供了高度可配置的线程池实现,允许开发者设置线程池的核心线程数、最大线程数、线程空闲时间等。

ScheduledThreadPoolExecutor: 支持基于时间调度的任务执行,例如定时任务或周期性任务。

2. 并发集合 (Concurrent Collections)

并发集合支持在多线程环境下安全地操作数据结构,避免竞争条件。

主要类:

ConcurrentHashMap: 高效的并发哈希表,实现了分段锁机制,支持并发的读写操作。

CopyOnWriteArrayList 和 CopyOnWriteArraySet: 每次修改都会复制整个数组,适合读多写少的场景。

ConcurrentLinkedQueue: 非阻塞的并发队列,适合高并发环境。

BlockingQueue: 支持线程安全的阻塞队列,包括 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue。

3. 同步工具 (Synchronization Utilities)

提供了各种同步原语,用于协调线程间的执行顺序和状态。

CountDownLatch: 允许一个或多个线程等待其他线程完成一组操作。

CyclicBarrier: 让一组线程等待彼此的执行完成,并在所有线程都到达屏障点后继续执行。

Semaphore: 控制多个线程访问共享资源的并发数量。

Exchanger: 让两个线程交换数据。

4. 并发锁 (Locks)

提供了灵活的锁机制,取代了传统的 synchronized 关键字,支持更复杂的锁控制。

ReentrantLock: 可重入锁,支持显式加锁和解锁,具有更灵活的特性,例如可中断的锁获取、超时获取锁等。

ReentrantReadWriteLock: 读写锁,允许多个读线程并发执行,但写操作是独占的,适合读多写少的场景。

StampedLock: 提供乐观读锁,适合读操作占多数的场景,相较于 ReentrantReadWriteLock 具有更高的性能。

5. 原子变量 (Atomic Variables)

提供了一组基于硬件级别的无锁操作,确保对变量的原子操作,避免了锁的开销。

主要类:AtomicInteger, AtomicLong, AtomicReference, AtomicBoolean

支持原子操作的方法:getAndIncrement(), compareAndSet(), getAndUpdate()等。

6. Fork/Join 框架

Fork/Join 框架用于并行执行任务,特别适合大规模数据处理或递归任务的并行计算。

ForkJoinPool: 是一个用于处理大量并行任务的线程池,基于工作窃取算法,提高了并发性能。

RecursiveTask 和 RecursiveAction: 是两种抽象类,用于定义可以分割的任务,RecursiveTask 有返回值,RecursiveAction 没有返回值。

7. Future 和 Callable

Callable: 类似于 Runnable,但可以返回结果并且能够抛出异常。

Future: 表示异步计算的结果,可以通过 get() 方法阻塞获取结果,也可以取消任务。

8. CompletionService

CompletionService 结合了 Executor 和 BlockingQueue 的功能,允许异步提交任务,并通过阻塞队列获取其完成结果。

ExecutorCompletionService: 是 CompletionService 的一种实现,它允许任务的执行结果按完成顺序返回。

9. Parallel Streams (并行流)

Java 8 中,JSR-166 的一些并发工具和思想被引入到 java.util.stream 中,支持并行流处理。并行流能够将数据处理任务拆分成多个子任务并发执行,提高处理效率。