jmm知识点

原子性:
对象类型:
对象地址读写,原子操作
并发读不可变状态,线程安全
并发读写可变状态,非线程线程
基本类型:
int,char数值读写,原子操作
long,double高低位,非原子操作
i++等组合操作,非原子操作

可见性:
final
初始化final字段确保可见性
volatile
读写volatile字段确保可见性
synchronized
同步块内读写字段确保可见性
happen before
遵守happen before次序可见性
lock
lock确保可见性,但重入的第二个锁应该不具有该特性
可排序:
Happen Before 法则
程序次序法则
如果A一定在B之前发生,则happen before,
监视器法则
对一个监视器的解锁一定发生在后续对同一监视器加锁之前
Volatie变量法则
写volatile变量一定发生在后续对它的读之前
线程启动法则
Thread.start一定发生在线程中的动作之前
线程终结法则
线程中的任何动作一定发生在括号中的动作之前(其他线程检测到这个线程已经终止,从Thread.join调用成功返回,Thread.isAlive()返回false)
中断法则
一个线程调用另一个线程的interrupt一定发生在另一线程发现中断之前。
终结法则
一个对象的构造函数结束一定发生在对象的finalizer之前
传递性
A发生在B之前,B发生在C之前,A一定发生在C之前。

@NotThreadSafe
public class UnsafeSequence {
private int value;

/* Returns a unique value. /
public int getNext() {
return value++;
}
}

if (!vector.contains(element)) {
vector.add(element);
}

//高并发跳过验证
if (condition) {
then ….
}

=================
public class NoVisibility {
private static boolean ready;
private static int number;

private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}

重排序

保证可见性
禁止重排序
不能保证原子性

class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
}
}
}

volatile实现
内存屏障
基于保守策略的JMM内存屏障插入策略:

在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。

final
. Assuming the object is constructed “correctly”, once an object is constructed, the values assigned to the final fields in the constructor will be visible to all other threads without synchronization. In addition, the visible values for any other object or array referenced by those final fields will be at least as up-to-date as the final fields.

What does it mean for an object to be properly constructed? It simply means that no reference to the object being constructed is allowed to “escape” during construction

public FinalFieldExample() { // bad!
x = 3;
y = 4;
// bad construction - allowing this to escape
global.obj = this;
}

class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}

static void writer() {
f = new FinalFieldExample();
}

static void reader() {
if (f != null) {
int i = f.x;
int j = f.y;
}
}
}

Initialization safety guarantees that for properly constructed objects, all threads will see the correct values of final fields that were set by the constructor, regardless of how the object is published. Further, any variables that can be reached through a final field of a properly constructed object (such as the elements of a final array or the contents of a HashMap referenced by a final field) are also guaranteed to be visible to other threads.

For objects with final fields, initialization safety prohibits reordering any part of construction with the initial load of a reference to that object. All writes to final fields made by the constructor, as well as to any variables reachable through those fields, become “frozen” when the constructor completes, and any thread that obtains a reference to that object is guaranteed to see a value that is at least as up to date as the frozen value. Writes that initialize variables reachable through final fields are not reordered with operations following the post-construction freeze.

@Immutable
class OneValueCache {
private final BigInteger lastNumber;
private final BigInteger[] lastFactors;

public OneValueCache(BigInteger i,
BigInteger[] factors) {
lastNumber = i;
lastFactors = Arrays.copyOf(factors, factors.length);
}

public BigInteger[] getFactors(BigInteger i) {
if (lastNumber == null || !lastNumber.equals(i))
return null;
else
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}
@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
private volatile OneValueCache cache =
new OneValueCache(null, null);

public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if (factors == null) {
factors = factor(i);
cache = new OneValueCache(i, factors);
}
encodeIntoResponse(resp, factors);
}

安全发布
Safe publication makes all the values written before the publication visible to all readers that observed the published object

// Unsafe publication
public Holder holder;

public void initialize() {
holder = new Holder(42);
}

There are a few trivial ways to achieve safe publication:

Exchange the reference through a properly locked field (JLS 17.4.5)

Use static initializer to do the initializing stores (JLS 12.4)

Exchange the reference via a volatile field (JLS 17.4.5), or as the consequence of this rule, via the AtomicX classes

Initialize the value into a final field (JLS 17.5).

====+++++

public class SynchronizedCLFactory { //效率差
private Singleton instance;

public Singleton get() {
synchronized (this) {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
}

public class UnsafeDCLFactory { //线程不安全
private Singleton instance;

public Singleton get() {
if (instance == null) { // read 1, check 1
synchronized (this) {
if (instance == null) { // read 2, check 2
instance = new Singleton();
}
}
}
return instance; // read 3
}
}

In JMM-ese speak, there is no happens-before between the initializing stores in Singleton constructor, and the reads of Singleton fields.

One of the intents of the Java Memory Model is to allow reorderings for ordinary reads, otherwise the performance costs would be prohibitive. Specification-wise, as mentioned in happens-before consistency rules, a read action can observe the unordered write via the race. This is decided for each read action, regardless what other actions have already read the same location. In our example, that means that even though “read 1” could read non-null instance, the code then moves on to returning it, then it does another racy read, and it can read a null instance, which would be returned!

静态初始化
public class HolderFactory {
public static Singleton get() {
return Holder.instance;
}

private static class Holder {
public static final Singleton instance = new Singleton();
}
}

public class SafeDCLFactory {
private volatile Singleton instance;

public Singleton get() {
if (instance == null) { // check 1
synchronized(this) {
if (instance == null) { // check 2
instance = new Singleton();
}
}
}
return instance;
}
}

a single non-synchronized read of wrapper
public class FinalWrapperFactory {
private FinalWrapper wrapper;

public Singleton get() {
FinalWrapper w = wrapper;
if (w == null) { // check 1
synchronized(this) {
w = wrapper;
if (w == null) { // check2
w = new FinalWrapper(new Singleton());
wrapper = w;
}
}
}
return w.instance;
}

private static class FinalWrapper {
public final Singleton instance;
public FinalWrapper(Singleton instance) {
this.instance = instance;
}
}
}

there still no happens-before between publishing the Singleton instance, and reading of any of its fields.
public class UnsafeLocalDCLFactory implements Factory {
private Singleton instance; // deliberately non-volatile

@Override
public Singleton getInstance() {
Singleton res = instance;
if (res == null) {
synchronized (this) {
res = instance;
if (res == null) {
res = new Singleton();
instance = res;
}
}
}
return res;
}
}

a performance optimization for SafeDCLFactory
public class SafeLocalDCLFactory implements Factory {
private volatile Singleton instance;

@Override
public Singleton getInstance() {
Singleton res = instance;
if (res == null) {
synchronized (this) {
res = instance;
if (res == null) {
res = new Singleton();
instance = res;
}
}
}
return res;
}
}

Map cache = new HashMap();
synchronized(cache) { // wait
value = cache.get(key);
if (value == null) { value = load(key); // slow
cache.put(key, value);
}
}

ConcurrentMap cache = new ConcurrentHashMap();
value = cache.get(key);
if (value == null) { value = load(key); // repeat
cache.putIfAbsent(key, value);
}

Map cache = new HashMap();
value = cache.get(key); //cpu 100%
if (value == null) {
synchronized(cache) {
value = cache.get(key);
if (value == null) { value = load(key);
cache.put(key, value);
}
}
}
Map cache = new ConcurrentHashMap();
value = cache.get(key);
if (value == null) {
synchronized(cache) {
value = cache.get(key);
if (value == null) { value = load(key);
cache.put(key, value);
}
}
}

static class Node

### JMM 的基本概念 Java 内存模型(JMM, Java Memory Model)是 Java 虚拟机的一部分,用于定义多线程程序在共享内存中的交互规则。它的核心目标在于为线程间操作提供一致的行为规范,确保多线程环境下数据的一致性和可见性[^1]。 JMM 不仅是一个抽象的概念框架,还涉及具体的实现细节,比如如何利用硬件特性(如缓存一致性协议、内存屏障等),从而保障多线程程序的正确运行[^2]。 --- ### JMM 的主要原理 #### 1. **内存分区结构** JMM 定义了一个抽象的内存结构,其中主要包括主内存(Main Memory)和工作内存(Working Memory)。 - 主内存存储的是实例对象及其字段的实际值。 - 工作内存则是每个线程私有的区域,保存该线程使用的变量副本。当线程读取某个变量时,会先从主内存加载到自己的工作内存;写入时,则需将更新后的值刷新回主内存[^3]。 这种设计使得不同线程之间无法直接访问彼此的工作内存,而必须通过主内存进行通信。 #### 2. **三大特性** ##### (1) **原子性** 指不可分割的操作,在同一时刻只能由一个线程完成。然而需要注意的是,某些看似简单的操作可能并非真正意义上的原子操作。例如 `i++` 实际上包含了三个步骤:读取 i 的当前值、计算新值并将其写回。如果多个线程同时执行此语句,就可能出现竞态条件[^4]。 为了保证复杂操作的原子性,通常借助锁机制(如 synchronized 或 ReentrantLock)或者无锁算法(如 CAS 循环比较交换技术)来实现。 ##### (2) **可见性** 指的是当一个线程修改了某共享变量后,其他线程能够立即感知这一变化的能力。由于现代计算机体系架构中存在高速缓存层,可能导致最近一次更改尚未同步至主存便被另一个 CPU 核心所获取的情况发生。为此,JMM 提供了一些关键字支持,像 volatile 就是用来强制指定每次对该标记位属性的访问都必须直接作用于主存之上,而不是依赖本地拷贝版本。 ##### (3) **有序性** 即使源码层面按照既定顺序书写逻辑流程,但在实际编译优化过程中仍可能发生指令重排现象——即调整原本应该遵循先后次序的动作序列以便提升效率。不过这往往会造成意想不到的结果特别是在跨线程协作场景下尤为明显。因此引入 happens-before 关系作为约束手段之一,明确规定哪些情形之下允许打破常规排列规律而不影响最终效果评估标准。 以下是几个典型的 happens-before 规则: - 程序顺序规则:单一线程内的动作保持原有声明位置关系; - 监视器锁定规则:解锁前发生的事件必定早于后续加锁行为之前; - Volatile 变量规则:针对同一个 volatile 类型成员项而言,前面对其所做的任何改动必然优先反映给后来查询者看到的内容; - 线程启动/终止规则:新建子进程正式投入运转之后才能看见父进程中已完成初始化部分的状态设定情况同样适用于结束阶段处理完毕信号传递过程等等。 --- ### 示例代码展示 下面给出一段简单示例演示上述理论知识点的应用: ```java public class VisibilityExample { private static boolean ready; private static int number; public static void main(String[] args) throws InterruptedException { new Thread(() -> { while (!ready); // spin-wait until flag is set to true by another thread. System.out.println("Number: " + number); }).start(); Thread.sleep(100); number = 42; // Write operation on shared variable 'number'. ready = true; // Set the readiness indicator which should be visible across threads. /* Without proper synchronization or using a memory barrier such as declaring 'ready' as volatile, there's no guarantee that changes made here will become immediately apparent elsewhere */ } } ``` 在此例子当中如果没有采用适当措施的话很可能造成输出结果不符合预期因为第二个线程未必能及时察觉第一个线程已经改变了相应数值状态。 --- ### 总结 综上所述,深入理解 JMM 对开发人员来说非常重要,尤其是在构建高性能并发应用程序的时候。掌握好其背后的运作机理有助于我们更好地规避潜在风险点进而提高整体软件质量水平。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值