volatile:Java虚拟机提供的轻量级的同步机制(synchronized)
- 保证可见性(一个线程修改了主内存的值,其他线程立即得知改变)
- 不保证原子性
- 禁止指令重排
JMM(Java内存模型),工作内存中存储着主内存的变量副本拷贝。工作内存是每个线程的私有区域,JMM规定所有变量都存储在主内存,主内存是共享内存区域,所有线程均可访问,线程对变量的操作(读取赋值等)只能在工作内存操作,首先将变量从主内存拷贝到自己的工作内存空间,然后对变量操作,操作完成后将变量写回主内存,不能直接操作主内存的变量。(三大特性-1.可见性2.原子性3.有序性)
JMM模型volatile可见性示例代码:
/**
* @author luffy
*/
public class Main {
public static void main(String[] args) {
Data data = new Data();
new Thread(() ->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
data.dataChange();
},"AAA").start();
while (data.number == 0){
}
System.out.println("end "+ data.number);
}
}
class Data{
int number = 0; //可见性-volatile int number = 0;
void dataChange(){
this.number = 50;
}
}
原子性:不可分割,不可中断,要么全部执行成功,要么全部失败,保证数据的完整一致性。volatile不保证原子性示例代码:
/**
* @author luffy
*/
public class Main {
public static void main(String[] args) {
Data data = new Data();
for(int i =0 ;i< 20;i++){
new Thread(()->{
for(int j=0;j<1000;j++){
data.dataAdd();
}
},String.valueOf(i)).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(data.number);
}
}
class Data{
int number = 0 ;
void dataAdd(){
number++;
}
}
number++汇编指令
使用Atomic原子类保证原子性(CAS),示例代码:
/**
* @author luffy
*/
public class Main {
public static void main(String[] args) {
Data data = new Data();
for(int i =0 ;i< 20;i++){
new Thread(()->{
for(int j=0;j<1000;j++){
data.dataAdd();
}
},String.valueOf(i)).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(data.atomicInteger);
}
}
class Data{
AtomicInteger atomicInteger = new AtomicInteger();
void dataAdd(){
atomicInteger.getAndIncrement();
}
}
指令重排:源代码->编译器优化的重排->指令并行的重排->内存系统的重排->最终执行的指令(多线程)。指令重排需要考虑指令之间的数据依赖性。
通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化(禁止重排);强制刷出各种CPU的缓存数据(可见性)。
/**
* @author luffy
**/
public class Test {
int number = 0; //volatile修饰
boolean flag = false;
public void method1(){
this.number = 1;
this.flag = true; //指令可能重排,多线程中变量一致性无法保证
}
public void method2(){
if(flag){
number+=5;
System.out.println(number);
}
}
}
volatile用例:单例模式-DCL(双端检测)机制,示例如下:
/**
* @author luffy
**/
public class Test {
private static Test instance = null; //可能存在线程安全性问题,用volatile修饰
private Test(){
System.out.println(Thread.currentThread().getName()+"test 构造方法!");
}
public static Test getInstance(){
if (instance == null){
synchronized (Test.class){
if(instance == null){
instance = new Test();
}
}
}
return instance;
}
public static void main(String[] args){
for(int i =0 ;i< 10;i++){
new Thread(()->{
Test.getInstance();
},String.valueOf(i)).start();
}
}
}
instance = new Test();
//读取到的instance不为null,instance引用对象可能没有完成初始化
memory = allocate(); // 1 分配对象内存空间
instance(memory); // 2 初始化对象
instance = memory; // 3 设置instance指向分配的内存地址,此时!=null
步骤2和3不存在数据依赖关系,可能存在指令重排
网友评论