Volatile关键字
Volatile 是 Java 虚拟机提供 轻量级的同步机制(可理解为弱化版的synchronized)
作用
- 保证可见性
- 不保证原子性
- 禁止指令重排
保证可见性
示例
import java.util.concurrent.TimeUnit;
public class TestVolatile {
//加了volatile 是可以保证可见性的
private volatile static Integer number = 0;
public static void main(String[] args) {
new Thread(()->{
while (number==0){
//此处不能写有锁的方法
//(比如输出方法中含有synchronized,否则演示会失败)
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
number = 1;
System.out.println("主线程更改number后"+number);
}
}
属性number不加关键字Volatile ,经过运行我们发现程序不会停止进入了死循环。这是因为主线程对number进行修改后,在其它线程中对number属性的修改不可见,所以循环条件一直成立。解决办法就是给要改变的属性加上volatile关键字,保证属性被修改后的可见性。
不保证原子性
原子性:不可分割。某个线程在执行任务的时候,不能被其它线程打扰的,也不能被分割的。
volatile 是不保证原子性的。
示例
public class TestVolatile {
private static volatile int number = 0;
public static void add() {
//++ 不是一个原子操作,是2~3个操作
number++;
}
public static void main(String[] args) {
//下面线程执行完理论上 number === 20000
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
// main gc
Thread.yield();
}
//最终执行完的结果
System.out.println(number);
}
}
上面的例子运行结果不等于20000,并且每次的结果都不一样。这是因为volatile 是不保证原子性的。
问题:除了使用lock锁和synchronized锁外,还能使用什么方法保证原子性?
上面的示例中num++不是原子性操作。通过查看底层源码我们就能知道。
答:可以使用原子类进行操作,这样就可以保证原子性。
将上面的示例改为原子类操作就能保证原子性了。下面的例子结果为20000,是我们想要的结果。
import java.util.concurrent.atomic.AtomicInteger;
public class TestVolatile {
//private static volatile int number = 0;
private static volatile AtomicInteger number = new AtomicInteger();
public static void add() {
//++ 不是一个原子操作,是2~3个操作
//number++;
number.incrementAndGet();//底层是 CAS 保证原子性
}
public static void main(String[] args) {
//下面线程执行完理论上 number === 20000
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
// main gc
Thread.yield();
}
//最终执行完的结果
System.out.println(number);
}
}
使用原子类的效率比使用锁的效率高很多。
禁止指令重排
我们写的程序,计算机并不是按照我们自己写的那样去执行的。在执行过程中指令的顺序可能会发生改变。这就是指令重排。
处理器在进行指令重排的时候,会考虑数据之间的依赖性!,并不会盲目的指令重排,造成结果错误。
int x=1; //1
int y=2; //2
x=x+5; //3
y=x*x; //4
//我们期望的执行顺序是 1234
//可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的(这样排会造成结果错误)
示例:如果在下面例子中不禁止指令重排可能造成的影响结果如下:
前提:a b x y这四个值 默认都是0
线程 A | 线程B |
---|---|
x = a | y = b |
b = 1 | a = 2 |
正常的结果: x = 0; y =0;
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
如果不禁止指令重排可能在线程A中,先执行b=1,然后再执行x=a
在线程B中可能会出现,先执行a=2,然后执行y=b;
那么结果就可能是:x=2; y=1。
Volatile 如何用
- Volatile的一篇文章推荐
- 在单例模式中的DCL懒汉式中就用到了Volatile