Volatile关键字
Volatiile 的作用
1,防止指令重排
2,保证变量的可见性但是不能保证其原子性
防止指令重排:
public class Cat {
private static volatile Cat cat;
//私有化构造函数
private Cat(){};
private static Cat getCat(){
if(cat == null){
synchronized (cat){
if(cat == null){
cat = new Cat();
}
}
}
return cat;
}
/*为什么我们要在变量前面加上volatile关键字?
- 首先我们要了解对象的构造过程
- 1,给对象分配内存空间
- 2,初始化对象
- 3,将分配的内存空间地址赋值给对象的应用
- 由于操作系统对指令码的重新排序可能会变成
- 1,给对象分配内存空间
- 2,将分配好的内存空间地址赋值给对象的引用
- 3,初始化化对象
- 如果这个流程在多线成的情况下就可能见没有初始化的对象医用暴露出来,出现不可预测的结果,因此为了防止
- 这个指令重排我们需要将变量变成volatile类型的变量;
- */
可见性
指一个线程修改了共同变量,而另一个线程不知道,查询的是之前没有修改的数据,这样会造成第二个线程查询的是脏数据,这是因为每个线程都有一个高速的缓存区–线程工作内存;vloatile关键字能够有效的解决这个问题;
public class VolatileTest {
static int a = 1;
static int b = 2;
public void Change(){
a = 3;
b = a;
}
public void print(){
System.out.println("b:"+b+"::a:"+a);
}
public static void main(String[] args) {
while (true){
final VolatileTest volatileTest = new VolatileTest();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
volatileTest.Change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
volatileTest.print();
}
}).start();
if(a == 3 && b == 1){
break;
}
}
}
}
原子性:
关于原子性,volatile关键字只对单词的读写保持原子性,
public class VolatileTest01 {
private static volatile int a = 0;
public void Change(){
a++;
}
public static void main(String[] args) throws InterruptedException {
final VolatileTest01 volatileTest01 = new VolatileTest01();
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
volatileTest01.Change();
}
}).start();
}
Thread.sleep(1000);//为了保证上面的代码能够全部执行完毕
System.out.println(a);
}
}
这样就可以看出volatile关键字不能够保证原子性(如果能够保证原子性的话结果应该是1000),因为a++是一个复合操作:
1,读取a的值;
2,2,将a+1;
3,3,将a的值再次写回内存
Volatile这个关键字不能保证这三个操作的原子性,我们可以通过加锁(synchronized)的方式实现他的原子性。
可见性的原因:
因为线程不是本身不是直接跟内存进行数据交换的,而是通过线程自身的工作内存完成的,所以会导致没有可见性;volatile是通过的将线程修改后的值强制的刷新到主内存中,然后会使别的线程的线程工作内存失效,需要重新在主内存中获取最新的值;