Java线程内存模型跟cpu缓存模型类似,是基于cpu缓存模型来建立的,java线程内存模型是标准化的,屏蔽掉了底层不同计算机的区别
注意:每个线程操作共享变量操作的是复制主内存的副本,当一个线程修改了自己工作内存中变量,对其他线程是不可见的,会导致线程不安全的问题。下面代码演示这种问题:
代码测试:
package com.lxf.volatileT;
import java.util.concurrent.TimeUnit;
/**
* 两个线程访问同一个共享变量,一个线程修改完共享变量后,另一个线程对这个共享变量是不可见的
*/
public class JMMDemo {
private static boolean flag=true;
public static void main(String[] args) {
new Thread(()->{//线程1
while (flag){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag=false;
System.out.println("flag="+flag);
}
}
结果:打印flag=true,但是线程1并未停止
解决方法:
package com.lxf.volatileT;
import java.util.concurrent.TimeUnit;
/**
* volatile实现实现之间共享变量可见
*volatile作用:线程之间可见、有序性(防止指令重排)、不能保证原子性
*/
public class JMMDemo {
private static volatile boolean flag=true;
public static void main(String[] args) {
new Thread(()->{//线程1
while (flag){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag=false;
System.out.println("flag="+flag);
}
}
在打印flag=true之后,线程1也停止了
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁。
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
对一个变量进行unlock操作之前,必须把此变量同步回主内存
Volatile不保证原子性代码测试:
package com.lxf.jmm;
public class VolatileAtomicTest{
public static volatile int num=0;
public static void increase(){
num++;
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads=new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i]=new Thread(()->{
for (int j = 0; j < 1000; j++) {
increase();
}
});
threads[i].start();
}
//等所有线程执行完了再执行main
for (Thread thread : threads) {
thread.join();
}
System.out.println("num="+num);
}
}
结果是<=10000,多次运行就能看到。
分析原因:
num++是一组操作(假设num值初始值为0),里面分为三步原子操作:读取到num值,num值加1,将num值写回缓存,这三步操作都有可能阻塞。假如一个线程读取num值后阻塞,然后另一个线程执行完了:将num赋值为1,然后这个线程继续执行将值仍然返回为1,这就造成了上面的问题。
参考博文:volatile为什么不能保证原子性
Volatile保证有序性测试(禁止指令重排)
package com.lxf.jmm;
import java.util.HashMap;
import java.util.Map;
public class VolatileSerialTest {
static int x=0,y=0;
public static void main(String[] args) throws InterruptedException {
//存结果的Map集合
Map<String,Integer> resultMap=new HashMap<>();
for (int i = 0; i < 1000000; i++) {
x=0;y=0;
resultMap.clear();
//第一个线程
Thread one=new Thread(()->{
int a=y;
x=1;
resultMap.put("a",a);
});
//第二个线程
Thread two=new Thread(()->{
int b=x;
y=1;
resultMap.put("b",b);
});
//第一个线程开启
one.start();
//第二个线程开启
two.start();
//第一个线程插队
one.join();
//第二个线程插队
two.join();
if(resultMap.get("a")==0&&resultMap.get("b")==0){
System.out.println("===============a等于0,b也等于0================================");
System.out.println("a=" + resultMap.get("a") + ",b=" + resultMap.get("b"));
System.out.println("===============a等于0,b也等于0================================");
}else if(resultMap.get("a")==1&&resultMap.get("b")==1){
System.out.println("===============a等于1,b也等于1================================");
System.out.println("a=" + resultMap.get("a") + ",b=" + resultMap.get("b"));
System.out.println("===============a等于1,b也等于1================================");
}else{
System.out.println("a=" + resultMap.get("a") + ",b=" + resultMap.get("b"));
}
}
}
}
结果可能性:
a=0,b=0
a=0,b=1
a=1,b=0
a=1,b=1
结果分析:前三个可能性我们都知道,当one线程在前结果就是a=1,b=0,当two线程在前结果就是a=0,b=1,当两个线程同时运行:a=0,b=0。但是a=1,b=1这种可能性还是难以理解,但还是存在的(很少见,得大量运行)见下图:
因为发生了指令重排序:
解决方法:定义x,y的时候加上volatile关键字就可以解决了
具体解释看以下博文:
参考博文:
1. 并发关键字volatile(重排序和内存屏障)
本文地址:https://blog.csdn.net/Inmaturity_7/article/details/107086864
如对本文有疑问, 点击进行留言回复!!
before社区电量是什么意思 Before社区电量获得方法
RecycleView入门详解(教你全面掌握RecycleView用法)
动态权限请求框架RxPermissions(几行代码搞定权限)
URL路径@PathVariable出现点号“.“时值遭截断问题
网友评论