1 volatile的可见性
在本地内存中,每次使用volatile型变量都必须从主内存中获取数值,这可以保证获取的是其它线程对这个volatile型变量所做的修改后的数值;每次修改volatile型变量都必须立刻同步到主内存中,这可以保证其它线程获取的是当前线程对这个volatile型变量所做的修改后的数值。
2 volatile禁止指令重排序
volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同。
volatile的禁止重排序并不局限于两个 volatile 的属性操作不能重排序,而且是 volatile 属性操作和它周围的普通属性的操作也不能重排序。
之前 instance = new Singleton() 中,如果 instance 是 volatile 的,那么对于 instance 的赋值操作(赋一个引用给 instance 变量)就不会和构造函数中的属性赋值发生重排序,能保证构造方法结束后,才将此对象引用赋值给 instance。
根据 volatile 的内存可见性和禁止重排序,那么我们不难得出一个推论:线程 a 如果写入一个 volatile 变量,此时线程 b 再读取这个变量,那么此时对于线程 a 可见的所有属性对于线程 b 都是可见的。
3 volatile的使用场景
volatile 修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值。在并发包的源码中,它使用得非常多。
示例三:volatile保证可见性,而synchronized保证原子性。
public class Test6 {
volatile int count = 0;
public synchronized void m() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public static void main(String[] args) {
Test6 t = new Test6();
List<Thread> threads = new ArrayList<>();
// 创建10个线程
for (int i = 0; i < 10; i++) {
threads.add(new Thread(new Runnable() {
public void run() {
t.m();
}
}));
}
// 启动线程
for (Thread thread : threads) {
thread.start();
}
// 确保10个线程执行完毕后,再执行主线程的输出
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(t.count);// 输出100000
}
}
网友评论