volatile 是什么?
- volatile是一种
同步机制
,比synchronized或者Lock相关类更轻量
,因为使用volatile并不会
发生上下文切换
等开销很大的行为。 - 如果一个变量被修饰成volatile,那么JVM就知道了这个变量可能
会被并发修改
。 - 虽然开销小,相应的能力也小,虽然说volatile是用来同步的保证线程安全的,但是volatile做不到synchronized那样
原子保护
,volatile仅在很有限的场景
下才能发挥作用。
volatile的适用场合?
适用场景一
boolean flag
, 如果一个共享变量自始至终只被各个线程赋值
,而没有其他的操作,那么就可以用volatile
来代替synchronized
或者代替原子变量,因为赋值自身是有原子性
的,而volatile
又保证了可见性,所以就足以保证线程安全。
package jmm;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 描述: 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;
}
}
其实这里也适用于int类型,但前提是直接赋值。中间不存在任何其他操作,如对比等。
以下案例线程安全吗?
package jmm;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 描述: 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;
}
}
以上代码多次运行后,我们发现其结果是不可持续的。因为在赋值之前他需要计算之前的bool值,然后再取反。这一过程并非原子操作。
适用场景二:作为刷新之前变量的触发器
volatile的两点作用:
-
保证可见性
。读一个volatile变量之前,需要先使相应的本地缓存失效,这样就必须到主内存读取最新变量值,写一个volatile属性会立即刷入到主内存。 -
禁止指令重排序优化
:解决单例双重锁乱序问题。
通过volatile关键字
禁止重排序的问题。
如何才能阻止x=0,y=0
这样的情况呢?
package jmm;
import java.util.concurrent.CountDownLatch;
/**
* 描述:禁止重排序的现象(x = 0, y=0) (直到达到 某个条件才停止,测试小概率事件)
*
*/
public class OutOfOrderExecution {
private volatile static int x = 0, y = 0;
private volatile 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; // x = b = 0
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
b = 1;
y = a; // y = a = 0
}
});
one.start();
two.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
之后,我们发现x=0,y=0这样的情况就不存在了(无论你执行多长时间)。
volatile和synchronized的关系?
volatile在这方面可以看做是轻量版synchronized
:如果一个共享变量自始至终只被各个线程赋值
,而没有其他的操作,那么就可以用volatile来代替syncronied或者代替原子变量,因为赋值自身是有原子性的,而volatile又保证了可见性
,所以就足以保证线程安全。
volatile小结
- volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如boolean flag;或者作为触发器,实现轻量级同步。
- volatile属性的读写操作都是无锁的,他不能替代synchronized,因为他
没有提供原子性和互斥性
。因为无锁
,不需要花费时间在获取锁和释放锁上
,所以说他是低成本的。 -
volatile只能作用于属性
,我们用volatile修饰符属性,这样compliers就不会对这个属性做指令重排序
。 -
volatile 提供了可见性
,任何一个线程对其的修改将立马对其他线程可见。volatile属性不会被线程缓存,始终从主存中读取。 - volatile提供了
happers-before
保证,对volatile变量v的写入happers-before所有其他线程后续对v的读操作。 - volatile 可以使得
long和double
的赋值是原子的。
能保证可见性的措施
除了volatile可以让变量保证可见性外,synchronized
,Lock
,并发集合
,Thread.join()
和Thread.start()
都可以保证可见性
。具体看happens-before
原则规定。
对synchronized可见性的正确理解
- synchronized不仅保证了原子性,还保证了
可见性
。 - synchronized不仅让保护的代码安全,还
近朱者赤
。
可参考该案例:
package jmm;
/**
* 描述: 演示可见性带来的问题(通过synchronized来保证可见性)
*/
public class FieldVisibilityABCD {
int a = 1;
int b = 2;
int c = 2;
int d = 2;
private void change() {
a = 3;
b = 4;
c = 5;
synchronized (this) {
d = 6;
}
}
private void print() {
synchronized (this) {
int aa = a;
}
int bb = b;
int cc = c;
int dd = d;
System.out.println("b=" + b + ";a=" + a);
}
public static void main(String[] args) {
while (true) {
FieldVisibilityABCD test = new FieldVisibilityABCD();
new Thread(() -> {
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();
}
}
}
网友评论