valotile关键字
当一个变量定义为valotile
之后,就保证了此变量对所有线程的可见性,可见性是指:当一个线程改变了这个变量之后,新值对于其他线程来说是可以被立即感知的。在读取一个volatile字段之前,各线程中缓冲区必须失效,因为值是存在于主存中而不是各线程中缓冲区。而普通变量不能做到这一点,普通变量的值在线程间传递需要通过主内存来完成。如:线程A修改了一个普通变量的值,然后向主内存进行回写,另外一个线程B在线程A回写完成之后再从主内存进行读取操作,新变量值才会对线程B可见
测试用例:
public class VolatileTest {
private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(1);
private static volatile int index = 1;
public static void main(String[] args) throws InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
Thread[] threads = new Thread[20];
for (int i=0; i < 20; i++){
threads[i] = new Thread(){
@Override
public void run() {
try {
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 10000; j++){
index ++;
ATOMIC_INTEGER.incrementAndGet();
}
}
};
threads[i].start();
}
Thread.sleep(200);
startLatch.countDown();
for (Thread t: threads){
t.join();
}
System.out.println("AtomicInteger最终运行结果: "+ ATOMIC_INTEGER.get());
System.out.println("volatile修饰参数运行结果: "+ index);
}
}
结果:
// 可能结果1
AtomicInteger最终运行结果: 200001
volatile修饰参数运行结果: 195631
// 可能结果2
AtomicInteger最终运行结果: 200001
volatile修饰参数运行结果: 200001
使用valotile
关键字,并不能保证在多线程环境下变量的线程安全问题,因为它只保证了可见性
和禁止指令重排(instruttive-reorder)
,并不保证原子性
, 禁止指令重排
保证了在线程内在指令执行的串行性
简而言之,volatile变量自身具有下列特性:
- 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
- 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
volatile写-读的内存语义
- volatile写的内存语义: 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
- volatile读的内存语义:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
final
和Synchronized
关键字也是禁止指令重排的