问题:谈一谈你对volatile关键字的理解
volatile 是Java虚拟机提供的轻量级的同步机制
1. 保证可见性
2. 不保证原子性
3. 禁止指令重排
-
volatile引出了另一个问题 JMM
-
概念: Java Memory Model (Java 内存模型 (不是JVM 内存模型)) ,JMM 是一种规范或者规则,抽象出来的,不是真实存在的,通过这种规范定义了程序中各个变量的访问机制
-
特点:由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为他们创建一个工作内存(栈空间),工作内存是每个线程的私有数据区域,而Java内存模型规定了所有的变量都存储在主内存中,主内存是共享的,所有线程都可以访问,但是线程对变量的操作必须在自己的工作内存中进行,首先要从主内存中拷贝一份副本到自己的工作内存中,然后对变量进行操作,操作完成后再写回主内存。注意:线程不能直接操作主内存的变量,变量副本拷贝,不同的线程无法访问对方的工作内存,线程之前的通信通过主内存来完成的
-
三个特性:
- 可见性
- 原子性
- 有序性
-
-
理解了JMM ,之后再回到volatile这个关键字上
-
volatile的可见性:
-
验证volatile的可见性
-
假如 int number =0;number变量之前根本没有添加volatile关键字修饰,没有可见性
-
添加volatile,可以解决可见性问题
//资源类 class MyData { int number = 0; public void addToNumber() { number = 60; } } public class VolatileDemo { public static void main(String[] args) { MyData myData = new MyData(); new Thread( () -> { System.out.println(Thread.currentThread().getName() + "\t come in"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } myData.addToNumber(); System.out.println(Thread.currentThread().getName() + "\t updated:" + myData.number); } , "AAA").start(); while (myData.number == 0) { //如果Main感知到number值的更改程序就不会死循环 } System.out.println(Thread.currentThread().getName() + "\t miss:" + myData.number); } }
-
这段程序的结果是死循环 两个线程 一个是AAA线程 一个是Main 线程,解决办法是在 number变量前 加 volatile
-
2.验证volatile不保证原子性
- 不可分割,完整性,即就是某个线程在做某个具体业务时,中间不可以被阻塞或者分割。要么成功,要么失败
//写覆盖,写丢失
/*怎么解决:
* 1. synchronized(太大了)
* 2.用juc下的原子类
*/
class MyData {
volatile int number = 0;
AtomicInteger atomicInteger=new AtomicInteger();
public void addToNumber() {
number = 60;
}
/**
* 请注意加了 volatile
* <p>
* number++; 3步
*/
public void addPlusPlus() {
number++;
}
public void addAtomic(){
atomicInteger.getAndIncrement();
}
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(() ->
{
for (int j = 1; j <= 1000; j++) {
myData.addPlusPlus();
myData.addAtomic();
}
}, String.valueOf(i)
).start();
}
//需要等待上面20个线程全部计算完成后,再用main 线程取得最终的结果值看是多少
//main gc
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t int,finally number:" + myData.number);
System.out.println(Thread.currentThread().getName() + "\t AtomicInteger ,finally number:" + myData.atomicInteger);
}
}
3.volatile禁止指令重排
最后谈谈 volatile 的案例:
最经典的是单例模式:
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
/**
* DCL(Double Check Lock 双端检锁机制)
*/
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t 构造方法");
}
public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo.class) {
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
//单线程
// System.out.println(SingletonDemo.getInstance()==SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance()==SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance()==SingletonDemo.getInstance());
//并发多线程
for (int i = 1; i <= 1000; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}
}