volatile关键字
volatile是什么
- volatile是一种同步机制,比synchronized活着Lock相关类更轻量,因为使用volatile并不会发生上下文切换等开销很大的行为
- 如果一个变量被volatile修饰,那么JVM就知道了这个变量可能会被并发修改
- 但是开销小,相应的能力也小,虽说volatile是用来同步的保证线程安全的,但是volatile做不到synchronized那样的原子保护,volatile仅在很有限的场景下才能发挥作用
volatile的适用场合
- 适用场合1:boolean flag,如果一个共享变量自始至终只被各个线程赋值,而没有其他的操作,那么就可以用volatile来代替synchronized或者代替原子变量,因为赋值自身是有原子性的,而volatile又保证了可见性,所以就足以保证线程安全。因此volatile在这方便可以看做是轻量版的synchronized
- 适用场合2:作为刷新之前变量的触发器
- 不适用: a++
不适用于volatile的场景
/**
* 描述: 不适用于volatile的场景
*/
public class NoVolatile implements Runnable {
volatile int a;
AtomicInteger realA = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Runnable r = new NoVolatile();
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(((NoVolatile) r).a);
System.out.println(((NoVolatile) r).realA.get());
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a++;
realA.incrementAndGet();
}
}
}
不适用于volatile的场景2
/**
* 描述: volatile不适用的情况2
*/
public class NoVolatile2 implements Runnable {
volatile boolean done = false;
AtomicInteger realA = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Runnable r = new NoVolatile2();
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(((NoVolatile2) r).done);
System.out.println(((NoVolatile2) r).realA.get());
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
flipDone();
realA.incrementAndGet();
}
}
private void flipDone() {
done = !done;
}
}
适用volatile场景1
/**
* 描述: volatile适用的情况1
*/
public class UseVolatile1 implements Runnable {
volatile boolean done = false;
AtomicInteger realA = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Runnable r = new UseVolatile1();
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(((UseVolatile1) r).done);
System.out.println(((UseVolatile1) r).realA.get());
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
setDone();
realA.incrementAndGet();
}
}
private void setDone() {
done = true;
}
}
适用场景2::加了volatile后,就不会出现(0,0)情况了
/**
* 描述:演示重排序的现象"直到达到某个条件才停止",测试小概率事件
*/
public class OutOfOrderExecution {
private static int x = 0 , y = 0;
private static int a = 0 , b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for(; ;) {
i ++;
x = 0;
y = 0;
a = 0;
b = 0;
CountDownLatch latch = new CountDownLatch(3);
Thread one = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
b = 1;
y = a;
}
});
two.start();
one.start();
latch.countDown();
one.join();
two.join();
String result = "第" + i + "次(" + x + "," + y + ")";
if (x == 0 && y == 0) {
System.out.println(result);
break;
} else {
System.out.println(result);
}
}
}
}
volatile的作用
- 可见性:读一个volatile变量之前,需要先让相应的本地缓存失效,这样就必须到主内存读取最新值,写一个volatile属性会立即刷入到主内存
- 禁止指令重排序:解决单例双重锁乱序的问题
总结
- volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如:boolean flag;或者作为触发器,实现轻量级同步
- volatile属性的读写操作都是无锁的,他不能替代synchronized,因为他没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁上,所以它是低成本的
- volatile只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序
- volatile提供了可见性,任何一个线程对其的修改将立马对其他线程可见。volatile属性不会被线程缓存,始终从主存中读取
- volatile提供了happens-before保证,对volatile变量v的写入happens-before所有其他线程后续对v的读操作
- volatile可以使得long和double的赋值是原子的