📝 个人主页:程序员阿红🔥
🎉 支持我:点赞👍收藏⭐️留言📝
1. Java内存模型
Java 内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
这里的工作内存是 JMM 的一个抽象概念,也叫本地内存,其存储了该线程以读/写共享变量的副本。就像每个处理器内核拥有私有的高速缓存,JMM 中每个线程拥有私有的本地内存。
不同线程之间无法直接访问对方工作内存中的变量,线程间的通信一般有两种方式进行,一是通过消息传递,二是共享内存。Java 线程间的通信采用的是共享内存方式,线程、主内存和工作内存的交互关系如图:
JMM 体现在以下几个方面
- 原子性 - 保证指令不会受到线程上下文切换的影响
- 可见性 - 保证指令不会受 cpu 缓存的影响
- 有序性 - 保证指令不会受 cpu 指令并行优化的影响
1.1原子性
定义: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。
1.2可见性
1.2.1退不出的循环
package com.example.JMM;
/**
* 退不出的循环
*/
public class Code_01_Test {
// 易变
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(true){
if(!run) {
break;
}
}
});
t.start();
Thread.sleep(1000);
run = false; // 线程t不会如预想的停下来
}
}
- 程序在执行中并不会退出while循环,这是为什么呢?分析如下
t线程执行while循环,1秒过后,主线程run的值修改为false,想让t线程停下来,但是程序并没有停下来。
共享变量run存储在主存中,主线程和t线程共享该变量,分别缓存在自己的工作内存中。主线程修改的run值只会保存在自己的工作内存中,并不会同步到主存中,其次t线程也只会读取自己本地内存的run值,所以导致该while循环无法退出。
- 解决方法:
- 使用 volatile (易变关键字)
volatile主要作用:解决可见性问题
-
那什么又是可见性呢?
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
-
volatile它可以用来修饰成员变量和静态成员变量(放在主存中的变量),
-
他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,
-
线程操作 volatile 变量都是直接操作主存
package com.example.JMM;
/**
* 退不出的循环:
*/
public class Code_02_Test {
// 易变
static volatile boolean run = true;
// 注意 synchronized 语句块既可以保证代码块的原子性,
// 也同时保证代码块内变量的可见性。但缺点是 synchronized
// 是属于重量级操作,性能相对更低。
// synchronozied(JmmTest2.class){
// if(!run) {
// break;
// }
// }
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(true){
if(!run) {
break;
}
}
});
t.start();
Thread.sleep(1000);
run = false;
}
}
1.2.2可见性原理
- volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
- 对 volatile 变量的写指令后会加入写屏障
- 对 volatile 变量的读指令前会加入读屏障
1)写屏障(sfence)保证在该屏障之前的写操作都会同步到主存中。
public void actor2(I_Result r) {
num = 2;
ready = true; // ready 是被 volatile 修饰的,赋值带写屏障
// 写屏障
}
- 而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中的最新数据。
public void actor1(I_Result r) {
// 读屏障
// ready是被 volatile 修饰的,读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
1.3 有序性
-
写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
-
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public void actor1(I_Result r) {
// 读屏障
// ready 是被 volatile 修饰的,读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
注意:volatile 不能解决指令交错
写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证其它线程的读跑到它前面去。
而有序性的保证也只是保证了本线程内相关代码不被重排序
2.double-checked-locking问题
public class Double_Check_Lock {
public static void main(String[] args) {
Singleton singleton1 = new Singleton();
Singleton singleton2 = new Singleton();
System.out.println(singleton1.getSingleton() == singleton2.getSingleton());
}
}
class Singleton{
public static Singleton singleton = null;
public Singleton(){}
public static Singleton getSingleton(){
// 使用synchronized来保证线程安全问题
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
return singleton;
}
}
此方法还有些许不足,
- singleton是共享变量,使用了synchronized来进行保障线程安全问题,但是只要访问getSingleton就会对Singleton类对象加锁。
- synchronized是重量级锁,所以其性能会有所降低。
- 注意在 JDK 5 以上的版本的 volatile 才会真正有效。
3.double-checked locking 解决
- 加volatile就行了。
public class Double_Check_Lock_plus {
public static void main(String[] args) {
Singleton singleton1 = new Singleton();
Singleton singleton2 = new Singleton();
System.out.println(singleton1.getSingleton() == singleton2.getSingleton());
}
}
class SingletonPlus{
public static volatile SingletonPlus singleton = null;
public SingletonPlus(){}
public static SingletonPlus getSingleton() {
if (singleton == null) { //t2
// 使用synchronized来保证线程安全问题
synchronized (SingletonPlus.class) {
if (singleton == null) { //t1
singleton = new SingletonPlus();
}
}
}
return singleton;
}
}
1.为什么还要在synchronized外再加一个if (singleton == null)条件?
答:防止在此调用getSingleton方法还是会对其上锁,会严重影响其性能
2.但是if(INSTANCE == null)判断代码没有在同步代码块synchronized中
不能享有synchronized保证的原子性,可见性。(synchronized可以保证
其重排序,但是所有访问的共享资源必须都在synchronized内)
好比下图 t1线程处运行至此,t2线程运行到该位置。t1实例化该对象后,t2
线程并不能立即识别到singleton已经被实例化。
解决办法:增加volatile对其singleton对象来保证可见性。
💖💖💖 完结撒花
💖💖💖 路漫漫其修远兮,吾将上下而求索
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
最后,还不动手进你的收藏夹吃灰😎😎😎
🎉 支持我:点赞👍收藏⭐️留言📝