一、Volatile介绍
volatile 是java虚拟机提供的一种
轻量级
的同步机制,特性如下:
- 保证可见性
-不保证原子性
会发生写覆盖的情况,一顿操作猛如虎,一看原值还是1
- 禁止指令重排JMM Java内存模型
Java Memory Model 本身是一种抽象的概念,
本身并不真实存在
。它描述的是一组规范,通过这组规范定了程序种各个变量(包括实例字段、静态字段、和构成数组对象的元素)的访问方式。
JMM关于同步规定:
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前, 必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
JMM 三大特性:
- 可见性: 只要当子线程将数据写回到主内存时,其他子线程都将及时接受到消息。
- 原子性: 不可分割,完整性,也即某个线程正在操作某个具体业务时,中间不可以被加塞或者被分割,要不同时成功,要么同时失败。
- 有序性:
- 代码证明Volatile的可见性及JMM内存模型
public class MainApp {
// 如果没有volatile修饰,线程B将一直死循环
private volatile static int a = 0;
public static void main(String... args) throws Exception {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t come in");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 10;
}, "AA").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t come in");
while (true) {
if (a == 10) {
System.out.println("value has been changed");
break;
}
}
}, "BB").start();
}
}
二、Volatile 不保证原子性解决
问题原因: 线程A在回写数据时,被其他线程加塞导致线程A挂起,当其他线程回写完毕,准备通知其他线程时,线程A直接执行了写的操作。
解决方案
- 加synchronized
- 使用Atomic
三、禁止指令重排
什么是指令重排?
案例1:
四、如何保证线程安全
package com.fullstack.atguigu;
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\tsingleton was created...");
}
/**
* 方法1: 直接在method上添加synchronized,锁住整个方法,虽然解决了并发的问题,但是降低了性能
*/
public static synchronized SingletonDemo getInstance() {
if (instance == null) {
instance = new SingletonDemo();
}
return instance;
}
/**
* 方法2: 使用synchronized代码块锁住部分代码,注意要使用DCL(Double check Lock)
* 同时防止指令重排,最好使用volatile修饰
*/
public static SingletonDemo getInstance2() {
if (instance == null) {
synchronized (SingletonDemo.class) {
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
/**
* 在多线程模式下,如果没有加锁,会导致instance被初始化多次
*/
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
SingletonDemo.getInstance2();
}, "T-" + i).start();
}
}
}