1.什么是volatile?
volatile是java中的关键字,用来修饰会被多个线程访问和修改的变量
2.为什么要使用volatile
在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。
1.原子性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。volatile只能保证单步原子性,不能保证多步原子性,会发生覆盖的情况。
volatile不适用于变量++或者取!的情况,而是适用于赋值的情况
代码演示
volatile修饰变量++会产生的问题
public class NoVolatile implements Runnable {
volatile int a; //使用Volatile修饰变量a
AtomicInteger atomicInteger = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Runnable r = new NoVolatile();
//设置线程
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
//线程启动
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(((NoVolatile) r).a);//调用每个线程执行一万次之后的变量a结果
System.out.println(((NoVolatile) r).atomicInteger);//调用每个线程执行一万次之后的原子类产生的结果
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a++; //++不属于原子性操作,需要先读取a的值,再计算a+1的值,最后赋值给a,这个操作可以被打断
atomicInteger.incrementAndGet();
}
}
}
从运行的结果可以看出使用volatile修饰的变量并没有达到我们预期的结果,而原子类所产生的结果表明实际上是运行了两万次,这样可以充分证明变量++这种复合操作使用volatile修饰并不会生效
2.可见性问题
如果不加volatile修饰的话每个线程会拷贝一份主存中的变量,某一个线程修改了其他线程是不知道的。 而用volatile修饰的话多个线程访问同一个变量时,其中某一个线程修改了变量的值时其他线程都可以立刻看到修改后的值。 被volatile修饰的变量在被修改后会强制的修改到主存中,而其他线程访问自己的内存中的该变量时会发现其用volatile修饰会将内部缓存的值设为无效去主存中读取。
代码演示
public class FieIdVisibility {
int a = 1;
int b = 2;
private synchronized void change() {
a = 3;
b = a;
}
private void print() {
System.out.println("a=" + a + ";b=" + b);
}
public static void main(String[] args) {
while (true) {
FieIdVisibility test = new FieIdVisibility();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.print();
}
}).start();
}
}
}
当第一个线程执行change()方法时,执行到a=3;时,进程可能会分配到第二个线程会进行执行,此时读取a的值还是1,这就是可见性问题
如何解决可见性问题,使用volatile修饰变量,线程执行到volatile修饰的变量时,可以确保改变量之前的代码都已经执行过了,确保了可见性
public class UseVolatile {
int a = 1;
volatile int b = 2;
int c = 3;
private void change() {
a = 3;
c = 10;
b = 0;//如果执行到这行代码的话,他之前的代码都确保已经执行过了
}
private void print() {
if (b == 0) {
System.out.println("a=" + a + ";b=" + b + ";c=" + c);
}
}
public static void main(String[] args) {
while (true) {
UseVolatile test = new UseVolatile();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.print();
}
}).start();
}
}
}
3.有序性问题
什么是指令重排? 指令重排是JVM将代码编译成指令出于优化的考虑将指令执行顺序重排。 volatile如何防止指令重排? volatile用内存屏障实现有序性,在指令之前插入屏障指令,确保执行该指令之前所有指令都被执行完毕。
3.怎么使用volatile
使用volatile必须具备以下2个条件:
1.对变量的写操作不依赖于当前值
2.该变量没有包含在具有其他变量的不变式中
本篇关于volatile的概括就到此结束了,希望能够帮助到一些小伙伴,谢谢观看!