Volatile关键字
- Volatile是Java虚拟机提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排序
要想对Volatile有更好的了解Volatile就需要来谈谈JMM
1、JMM
JMM(Java内存模型)本身是一种抽象的概念并不真实存在,它描述的是一组规范或者规则,通过这组规范定义了程序中的各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
1.1、JVM和JMM
JVM:Java虚拟机
JMM:Java内存模型
1.2、JMM关于同步的规定
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的自有数据空间,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先将变量从主内存拷贝在自己的工作内存,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间通信(传值)必须通过主内存来完成,其简要访问过程如下图:
1.3、JMM的三大特性
1、可见性
2、原子性
3、有序性
2、Volatile保证可见性
import java.util.concurrent.TimeUnit;
public class JMMDemo {
//不加volatile程序就会死循环
//加volatile保证程序的可见性
private volatile static int num = 0;
public static void main(String[] args) {//main线程
new Thread(()->{//线程1
while (num == 0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
3、Volatile不保证原子性
- 原子性:不可分割,完整性,即某个线程在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整。要么同时成功,要么同时失败
public static void main(String[] args) {
Mydate mydate = new Mydate();
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
public void run() {
for (int j = 1; j <= 1000; j++) {
mydate.add();
}
}
},String.valueOf(i)).start();
}
//需要等待20个线程都计算完了,再用main线程取得最终的结果
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" finally number:"+mydate.number);
}
class Mydate{
volatile int number = 0;
public void addTo60() {
this.number = 60;
}
//加了关键字volatile修饰的
public void add() {
number++;
}
}
- 结果
main finally number:19491(每次结果都会不一样)
怎么解决
- 加synchronized
- 使用AtomicInteger
代码:
public class Test {
public static void main(String[] args) {
Mydate mydate = new Mydate();
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
public void run() {
for (int j = 1; j <= 1000; j++) {
mydate.add();
mydate.addMyAtomic();
}
}
},String.valueOf(i)).start();
}
//需要等待20个线程都计算完了,再用main线程取得最终的结果
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" int type finally number:"+mydate.number);
System.out.println(Thread.currentThread().getName()+" AtomicInteger type,finally number:"+mydate.atomicInteger);
}
}
class Mydate{
volatile int number = 0;
public void addTo60() {
this.number = 60;
}
//加了关键字volatile修饰的
public void add() {
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic() {
atomicInteger.getAndIncrement();
}
}
结果:
main int type finally number:19662
main AtomicInteger type,finally number:20000
4、禁止指令重排序
什么是指令重排:你写的程序,计算机并不是比按照你写的那样去执行de
源代码–>编译器优化的重排—>指令并行也会重排—>内存系统和也会重排—>执行
处理器在进行指令重排序的时候,考虑:数据之间的依赖性!
int x = 1;//1
int y = 2;//2
x = x + 5;//3
y = x * x;//4
//我们所期望的:1234 但是执行的时候可能会是 2134 1324
//可不可能是:4123 不可能
5、总结
-
工作内存与主内存同步延迟现象导致的可见性问题
可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见 -
对于指令重排序导致的可见性问题和有序性问题
可以利用volatile关键字来解决,因为volatile的另一个作用就是禁止重排序优化
7.单例模式
public class SingletonDemo {
private volatile static SingletonDemo instance = null;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName()+" 我是构造方法SingletonDemo()");
}
public static SingletonDemo getInstance() {
//双重校验锁
if(instance == null) {
synchronized (SingletonDemo.class) {
if(instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
new Thread(new Runnable() {
public void run() {
SingletonDemo.getInstance();
}
},String.valueOf(i)).start();
}
}
}