咱们一般会用volatile实现一些须要线程安全的代码(也有不少人不敢用,由于不了解),但事实上volatile自己并非线程安全的,相对于synchoronized,它有更多的使用局限性,只能限制在某些特定的场景。本篇文章的目的就是让你们对 volatile 在本质上有个把握,为了达到这个目的,咱们会从java 的内存模型及变量操做的内存管理来讲明(不用怕,你会发现很简单)。html
能够将内存简单分为两种:工做内存和主内存。全部的数据最终都须要存储在主内存,工做内存是线程独有的,线程之间无任何干扰。java的内存模型主要就是定义工做内存和主内存的交互,即工做内存如何从主内存拷贝数据,以入如何写数据。java 定义了8种原子性操做来完成工做内存与主内存的交互:java
这些操做也是有必定的条件限制的:
read 和load,store和write 必须成对出现,即从主内存中读数据的数据工做内存必须接受;传递到主内存的数据,也不能够被拒绝写入。
assign后的对象必须回写到缓存
未进行新赋值的对象不容许回写到主内存
新的变量只能在主内存产生,且未完成初始化的对象不容许在工做内存中使用
对象只容许被一条线程锁定,且能够被此线程屡次锁定
未被锁定的对象不容许执行unlock操做
对一个对象执行unlock以前,必须将对象回写到主内存
java的8种原子性操做,相互以前有必定的约束条件,但并无严格限制任意两个操做必须连续出现,只是表示成对出现,这也是为何会产生线程不安全性的缘由。
介绍了上述的背景知识,那咱们就来看一下volatile变量到底和普通变量有啥差异吧缓存
下面咱们用一个例子来讲明volatile变量与普通变量的区别。
假设有两个线程操做一个主内存的对象,且线程1早于线程2开始(以下例如示一个a++操做))安全
public class ThreadSafeTest {
public static int a = 0;
public static void increase() {
a++;
}
public static void main (String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
increase();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
increase();
}
}
});
t1.start();
t2.start();
}
}复制代码
线程2读取主内存对象(a)时,可能发生在几个时期:read以前、read以后、load以后、use以后、assign以后、 store以后、write以后(以下图所示);bash
假设线程1执行了a++,a从0变成了1,还将来得及写回主内存对象,线程2从主内存对象中读取的数据a=0;此时线程1写入主内存a=1,而线程2仍然执行完了a++ ,此时仍然 等于1(应该等于2),实际上,这就上至关于线程2 读入了一个过时的数据,致使线程不安全。ide
那若是将a变成volatile对象是否就正确了呢?
volatile对对象的操做作了更严格的限制:性能
assign以后必须紧跟store和write
实际至关于将read load use 三个原子操做变成一个原子操做;将assign-store-write变成一个原子操做。不少文章上都讲volatile对全部的线程是可见的,指的就是执行完了assign以后当即就会回写主内存;在任意一个线程读取主内存对象时,都会刷新主内存。在主内存中表现是数据一致性的,可是各线程内存当中却不必定是一致性的。
一样是上面的代码,换成volatile
```
public class ThreadSafeTest {
public static volatile int a = 0;spa
public static void increase() {线程
a++;复制代码
}3d
public static void main (String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
increase();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
increase();
}
}
});
t1.start();
t2.start();
}复制代码
}
```
运行后发现,也拿不到正确的结果(若是拿到请把j的数值调大)。操你妈,不是说是线程安全的变量吗?为啥也不正确?
这是由于线程内部的数据仍然有可能存在不一致性,好比,若是线程2读取数据时,处在线程1use以后,但线程1此时还将来得及回写主缓存,这时候线程2使用到的数据仍然是0,两个线程同时对0++,获得的结果只会是1,而不是理想中的2。
即然volatile 是非线程安全的,那要它还有什么用呢?若是你看过我写过的“线程安全”的文章应该知道,全部的对象都是相对线程安全的,也就是有条件的。volatile的线程安全固然也是有条件的,它是对synchronized这一重量级线程同步的一种补充,其总体性能上优于synchronized。那volatile的线程安全的条件是什么呢?适合使用在哪些场景?
《java虚拟机》给出两个条件:
那适合哪些场景呢?这个我就不一一举例了,一个哥门总结得很好,参考以下:www.ibm.com/developerwo…