Java内存模型
Java内存模型(JMM)是一个抽象概念,实际上并不存在。它描述了一组规则或规范,通过这些规则或规范可以定义程序中的各种变量(包括实例字段,静态字段和组成数组)。尝试屏蔽各种硬件和操作系统的内存访问差异,以便Java程序可以在各种平台上实现一致的内存访问效果。
请注意JMM和JVM内存区域划分之间的区别:
JMM描述了一套规则,以原子性,顺序和可见性为中心;
相似之处:共有公共区域和私有区域
主内存和工作内存
在处理器上读写寄存器的速度比内存快几个数量级。为了解决这种速度矛盾,在它们之间添加了一个缓存。
加入缓存带来了一个新问题:缓存一致性。如果多个缓存共享相同的主内存区域,则多个缓存的数据可能会不一致,并且需要达成一些共识才能解决此问题。
所有变量都存储在主存储器中。每个线程也都有其自己的工作内存。工作内存存储在缓存或寄存器中,该缓存或寄存器保存该线程使用的变量的主内存副本。
线程只能直接操作工作内存中的变量,并且不同线程之间的变量值传递需要通过主内存完成。
数据存储类型和操作方法
该方法中局部变量的基本类型将直接存储在工作存储器的堆栈帧结构中;
引用类型的局部变量:引用存储在工作存储器中,而实际上存储在主存储器中;
成员变量,静态变量和类信息都将存储在主存储器中;
主内存共享方法是每个线程将数据副本复制到工作内存,并在操作完成后将其刷新到主内存。
内存之间的交互作用
Java内存模型定义了8个操作来完成主内存和工作内存的交互操作。
读取:将变量的值从主存储器传输到工作存储器
load:读取后执行,将读取后的值放入工作存储器的变量副本中
使用:将工作存储器中变量的值传递给执行引擎
赋值:将从执行引擎接收到的值赋给工作存储器中的变量
存储:将工作存储器中变量的值传输到主存储器
写:存储之后执行,将存储获得的值放入主存储器中的变量
lock:作用在主存储器上的变量
解锁
订单重新排序的条件
在单线程环境中不能更改程序的运行结果;
如果存在数据依赖性,则不允许重新排序;
仅当无法通过“先发生原则”将其推出时,才能对这些指令进行重新排序。
记忆模型的三个特征
1.原子性
Java内存模型保证读取,加载,使用,分配,存储,写入,锁定和解锁操作是原子操作。例如,当对int类型变量执行赋值操作时,该操作是原子的。但是,Java内存模型允许虚拟机将未被volatile修改的64位数据的读取和写入操作(长,双)分为两个32位操作,即加载,存储,读取和写入。写操作可能不是原子操作。
存在误解,例如int之类的原子类型在多线程环境中不会出现线程安全问题。在前面的线程不安全示例代码中,cnt是int类型的变量。在1000个线程上执行自动递增操作后,获得的值为997,而不是1000。
为了便于讨论,将内存之间的交互操作简化为三个:加载,分配和存储。
下图说明了两个线程同时在cnt上运行。诸如加载,分配和存储之类的一系列操作并不是一个整体。然后,在T1处修改cnt且修改后的值尚未写入主存储器。 ,T2仍然可以读取旧值。可以看出,尽管这两个线程执行了两次增量操作,但主内存中的cnt值是1而不是2。因此,int类型的读写操作的原子性仅表明负载的单个操作,分配和存储都是原子的。
AtomicInteger可以保证多线程修改的原子性。
使用AtomicInteger重写以前不安全的代码,并获得以下线程安全实现:
公共类AtomicExample {
private AtomicInteger cnt = new AtomicInteger();
public void add(){
cnt.incrementAndGet();
}
public int get(){
返回cnt.get();
}
}复制代码
公共静态void main(String [] args)抛出InterruptedException {
final int threadSize = 1000;
AtomicExample示例=新的AtomicExample(); //仅修改此语句
最终CountDownLatch countDownLatch = new CountDownLatch(threadSize);
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0; i
executorService.execute(()-> {
example.add();
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println(example.get());
}复制代码
1000个复制代码
除了使用原子类之外,同步互斥锁也可以用于确保操作的原子性。其对应的内存间交互操作为:锁定和解锁,虚拟机实现中对应的字节码指令为monitorenter和monitorexit。

公共类AtomicSynchronizedExample {
private int cnt = 0;
公共同步的void add(){
cnt ++;
}
公共同步int get(){
返回cnt;
}
}复制代码
公共静态void main(String [] args)抛出InterruptedException {
final int threadSize = 1000;
AtomicSynchronizedExample示例=新的AtomicSynchronizedExample();
最终CountDownLatch countDownLatch = new CountDownLatch(threadSize);
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0; i
executorService.execute(()-> {
example.add();
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println(example.get());
}复制代码
1000个复制代码
2.可见度
可见性意味着当一个线程修改共享变量的值时,其他线程可以立即了解该修改。 Java内存模型通过在修改变量之后将新值同步回主存储器,并在读取变量之前从主存储器刷新变量值来实现可见性。 JMM的内部实现通常依赖于所谓的内存屏障,该内存屏障通过禁止某些重新排序方法来提供内存可见性保证,这意味着可以实施各种先发生后规则。同时,更复杂的是需要确保各种体系结构的各种编译器和处理器可以提供一致的行为。
可通过三种主要方法来获得可见性:
volatile,将强制变量本身和其他变量的状态从缓存中清除。
已同步,在对变量执行解锁操作之前,必须将变量值同步回主存储器。
final,一旦在构造函数中初始化了由final关键字修改的字段,并且没有发生此转义(其他线程通过此引用访问半初始化的对象),则其他线程可以看到final字段的值。
在前面的线程不安全示例中对cnt变量使用volatile修改不能解决线程不安全的问题,因为volatile无法保证操作的原子性。
3.秩序
排序的意思是:在此线程中观察,所有操作都是有序的。当一个线程观察到另一个线程时,所有操作均发生故障。乱序是由于指令重新排序造成的。在Java内存模型中,允许编译器和处理器对指令重新排序。重新排序过程不会影响单线程程序的执行,但会影响多个线程并发执行的正确性。
volatile关键字通过添加内存屏障来禁止指令重排,也就是说,在重新排序期间,以下指令不能放在内存屏障之前。
同步也可以用来确保顺序。这样可以确保一次只有一个线程执行同步代码,这相当于让线程按顺序执行同步代码。
发生之前(Happen-Before)
JSR-133内存模型使用首次出现的原理来确保Java内存模型中多线程操作的可见性。这也是早期语言规范中模糊可见性概念的精确定义。如上所述,易失性和同步性可用于确保顺序。此外,JVM还规定了优先出现的原则,这样一个操作可以在没有控制的情况下先于另一个操作完成。
由于指令的重新排序,两个操作之间存在事前发生的关系,这并不意味着必须在执行下一个操作之前执行上一个操作。仅要求上一个操作的执行结果对下一个操作可见,并且在第二个操作之前对上一个操作进行排序。
1.单线程原理(程序员顺序规则)
单线程规则
程中,程序之前的操作发生在程序之后的操作之前。
2.监视器锁定规则(监视器锁定规则)
显示器锁定规则
解锁(解锁)操作首先发生在同一锁上的后续锁定(锁定)操作中。
Java体系结构/分布式:705127209(达牛交易所集团)不要添加任何开发经验!
3.易变变量规则
易变变量规则
对volatile变量的写操作首先发生在对该变量的后续读取操作中。
4.线程启动规则
线程启动规则
对Thread对象的start()方法的调用先于该线程的每个动作。
5.线程加入规则
线程加入规则
当join()方法返回时,Thread对象的结尾首先出现。
6.线程中断规则
线程中断规则
当被中断线程的代码检测到发生中断事件时,首先会调用线程interrupt()方法,并且可以使用interrupted()方法来检测是否发生了中断。
7.对象终止规则
最终定律
对象初始化的完成(构造函数的执行结束)首先发生在其finalize()方法的开头。
8.传递性
传递性
如果操作A在操作B之前发生,并且操作B在操作C之前发生,则操作A在操作C之前发生。
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-367159-1.html
俺们那的人大部分谈生意都很实诚
不惜一战