以下都是个人理解,有大佬看到不对的请指教
在了解Volatile必须先JVM模型和JMM
JMM:JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式.
JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的.
从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:
线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。
JMM规范:
JMM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成
JMM同步:
线程加锁前,必须读取主内存的最新值到自己的工作内存
线程解锁前,必须把共享变量的值刷新回主内存
加锁解锁是同一把锁
简单来说,线程操作对象都是从:
主内存 ---> 拷贝 ---> 自己的工作内存 ---> 操作后 ---> 写回主内存
数据的读取传递:硬盘 < 内存 < cache(缓存) < CPU(计算)
JMM的三个特征可见性:当某个线程修改值后(线程工作内存中存储的是主内存中变量的副本),写回主内存后,其他线程立马知道。一句话:多线程中,一个线程对共享变量做了修改之后,其他的线程立即能够感知到该变量的这种修改。
原子性:一个操作不能被打断,要么全部执行完毕,要么不执行
有序性:计算机执行程序时,为了提高性能,编译器 和 处理器 经常会进行:指令重排
分三种:源码--->编译器优化重排--->指令并行重排--->内存系统重排--->最终执行处理器
在重排时,必须考虑指令之间的:数据依赖 ,控制依赖性
volatile:是java虚拟机提供的轻量级同步机制
两大特性:
保证可见性:
当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果 当一个线程向被volatile关键字修饰的变量写入数据的时候,虚拟机会强制它被值刷新到主内存中。 当一个线程用到被volatile关键字修饰的值的时候,虚拟机会强制要求它从主内存中读取
public class Test {
public volatile int num;
public void operate () {
this.num = 3;
}
//测试
public static void main(String[] args) {
Test test = new Test();
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+" ---》");
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
//修改
test.operate();
},"testOperate").start();
while (test.num == 0) {
System.out.println("--------------等待修改----------------------------");
}
System.out.println("修改了");
}
}
禁止指令重排
idea可以自己添加:external tool 查看
它是不能保证原子性的
public class Test {
public volatile int num;
public void operate () {
num++;
}
//测试
public static void main(String[] args) {
Test test = new Test();
//计算10*2000
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 2000; j++) {
new Thread(() -> test.operate()).start();
}
}).start();
}
//等待上面线程计算完成
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("正确结果:" + 10 * 2000);
System.out.println("实际结果:"+test.num);
}
}
解决不保证原子性的方式:
加:synchronized,lock... 原子类
public class Test {
// public volatile int num;
public volatile AtomicInteger num= new AtomicInteger();
public void operate () {
// num++;
num.getAndIncrement();
}
//测试
public static void main(String[] args) {
Test test = new Test();
//计算10*2000
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 2000; j++) {
new Thread(() -> test.operate()).start();
}
}).start();
}
//等待上面线程计算完成
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("正确结果:" + 10 * 2000);
System.out.println("实际结果:"+test.num.get());
}
}
DCL(双端检锁):
DCL会出现出现: 线程1分配了空间,发生指令重排,先建立了引用,随后cpu切换到了进程2 ,这个时候判断实例不为空拿到的半实例化的对象所出现的问题
有指令重排存在,test!=null的时候,test实列可能没有实例化完成,双重锁需要volatile修饰对象的,不然有安全问题
public class Test {
private static volatile Test test = null;
private Test(){
System.out.println(Thread.currentThread().getName()+"---------------------------");
}
public static Test getTest(){
if(null == test){
synchronized(Test.class) {
if(null == test){
test = new Test();
}
}
}
return test;
}
//测试
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> Test.getTest()).start();
}
}
}



