JUC_06
1、JMM java内存模型
Java 内存模型(JMM,Java Memory Model)是 Java 语言规范的一部分,用于定义多线程环境下的变量访问规则,特别是共享变量的可见性和有序性。
Volatil
作用:
1、保证可见性
2、不保证原子性
3、禁止指令重排
有序性: 程序的执行顺序是否会按照代码的书写顺序执行,JMM 会对操作进行一定程度的重排序优化。
指令重排:
源代码->编译器的优化重排->指令并行也可能重排->内存系统的重排->执行
内存屏障:1、保证特定的操作的执行顺序。
2、可以保证某些变量的内存可见性。
===可见性:
点击查看代码
package org.li;
import java.util.concurrent.TimeUnit;
public class TestVolatile {
private static int num=0;
public static void main(String[] args) {
new Thread(()->{
while(num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
num=1;
System.out.println("111");
}
}
点击查看代码
package org.li;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicDemo {
private static AtomicInteger num=new AtomicInteger();
public static void add(){
num.getAndIncrement();
System.out.println(num);
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 20000; j++) {
add();
}
}).start();
}
if (Thread.activeCount()>2){
Thread.yield();
}
TimeUnit.SECONDS.sleep(8);
System.out.println(Thread.currentThread().getName()+"======================="+num);
}
}
2、JMM数据同步八大原子操作
(1)lock(锁定):作用于主内存的变量,把一个变量标记为一条线程独占状态
(2)unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
(3)read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
(4)load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
(5)use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
(6)assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
(7)store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
(8)write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
如果一个变量从主内存中复制到工作内存中,就需要按顺序的执行read、load指令,如果是工作内存刷新到主内存则需要按顺序的执行store、write操作。但JMM只保证了顺序执行,并没有保证连续执行。

什么是内存屏障?
内存屏障,也称为内存栅栏(Memory Fence),是一种用于防止指令重排序的机制。它是CPU指令或JVM指令,用于在多线程编程中保证某些操作的顺序性和内存可见性。
内存屏障的种类
LoadLoad 屏障:
确保在LoadLoad屏障之前的所有读取操作完成后,屏障之后的读取操作才会执行。
例子:Load1; LoadLoad; Load2,在 Load2 之前,Load1 必须完成。
StoreStore 屏障:
确保在StoreStore屏障之前的所有写入操作完成后,屏障之后的写入操作才会执行。
例子:Store1; StoreStore; Store2,在 Store2 之前,Store1 必须完成。
LoadStore 屏障:
确保在LoadStore屏障之前的所有读取操作完成后,屏障之后的写入操作才会执行。
例子:Load1; LoadStore; Store2,在 Store2 之前,Load1 必须完成。
StoreLoad 屏障:
确保在StoreLoad屏障之前的所有写入操作完成后,屏障之后的读取操作才会执行。这是最强的屏障,通常会导致CPU流水线被清空,开销较大。
例子:Store1; StoreLoad; Load2,在 Load2 之前,Store1 必须完成。
内存屏障的应用
内存屏障用于在多线程环境中控制内存操作的顺序,以确保内存可见性和防止重排序,例如:
volatile关键字
在Java中,volatile关键字用于修饰变量,确保变量的读写操作对所有线程可见。
对volatile变量的写操作会插入一个 StoreLoad 屏障,确保之前的所有写操作都对其他线程可见,并阻止之后的读操作重排序到写操作之前。
锁(Locks)
锁机制(如 synchronized)会在加锁和解锁时插入内存屏障,以确保进入临界区的线程可以看到之前对共享变量的所有更新。
JMM中指令重排序与内存屏障的作用
指令重排序: 在Java内存模型中,为了优化程序的执行效率,编译器和处理器可能对指令进行重排序。但是这种优化在多线程环境下可能会导致线程安全问题,如不可见性和乱序执行。
内存屏障: 内存屏障通过阻止某些指令重排序来解决上述问题,确保多线程程序中指令的顺序性和内存操作的可见性。Java提供的 volatile、synchronized 等机制在底层都会插入适当的内存屏障来保证线程安全。
4.JMM中的Happens-Before规则
Happens-Before 规则是 JMM 的核心原则之一,用于定义操作之间的顺序关系:
程序顺序规则: 在一个线程内,代码的执行顺序按照程序代码的书写顺序。
监视器锁规则: 一个 unlock 操作在后续对同一锁的 lock 操作之前。
volatile 变量规则: 对一个 volatile 变量的写操作先行发生于后面对该 volatile 变量的读操作。
线程启动规则: Thread.start() 方法先行发生于此线程的每一个动作。
线程中断规则: 对线程的 interrupt() 调用先行发生于被中断线程的代码检测到中断事件。
线程终止规则: 线程的所有操作先行发生于此线程的终止检测(Thread.join() 返回,Thread.isAlive() 返回 false)。
对象终结规则: 对象的构造函数执行结束先行发生于 finalize() 方法的开始。
5.JMM在实际编程中的应用
理解 JMM 对于编写高效、安全的并发程序非常重要。以下是一些实际编程中的应用场景:
volatile 的正确使用: 当某个变量在多线程环境下被频繁读取且不需要复杂的同步操作时,可以使用 volatile 关键字。
synchronized 和 Lock 的选择: 在需要对共享资源进行原子操作且需要控制访问顺序时,使用 synchronized 或 Lock。
使用并发工具类: 尽量使用 java.util.concurrent 包提供的并发工具类(如 ConcurrentHashMap、BlockingQueue)来避免手动处理线程安全问题。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_61669379/article/details/141722230

浙公网安备 33010602011771号