标签:关系 相同 ring 防止 bool override instance lse 返回
volatile是java语言中的关键字,用来修饰会被多线程访问的共享变量,是JVM提供的轻量级的同步机制,相比同步代码块或者重入锁有更好的性能。它主要有两重语义,一是保证多个线程对共享变量访问的可见性,二防止指令重排序。
public class TestVolatile {
public static void main(String[] args) throws InterruptedException {
ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo).start();
threadDemo.flag = false;
System.out.println("已将flag置为" + threadDemo.flag);
}
static class ThreadDemo implements Runnable {
boolean flag = true;
@Override
public void run() {
System.out.println("Flag=" + flag);
}
}
}
当你多次执行代码时,有一定几率会出现这种结果
在主线程将子线程实例的flag置为false后,子线程中的flag竟然还是true。这是怎么回事?这就是多线程的内存可见性问题。对于一个没有volatile修饰的的共享变量,当一个线程对其进行了修改,另一线程并不一定能马上看见这个被修改后的值。为什么会出现这种情况呢?这就要从java的内存模型谈起。
java的内存模型定义了线程和主内存之间的抽象关系,它的内容主要包括:
线程,主内存,工作内存三者的交互关系如图所示
看看JMM模型会给我们在多线程环境下的读写带来什么样的问题。
这时就出现了共享变量在多线程环境下的可见性问题。如果把线程的工作内存当作主内存的缓存,这个问题的本质就在于如何解决缓存失效问题。那么JMM中是如何解决可见性问题的?这就不得不提到happens-before规则。
happens-before规则又叫先行发生规则。它定义了java内存模型中两项操作的偏序关系,更确切的说,它定义了操作可见性之间的偏序关系。比如A操作 happens-before B操作,并不意味这A操作一定在B操作之前,而是A操作的影响能被操作B观察到,这个影响包括改变了内存中共享变量的值,发送消息等。那么JMM定义了哪些happens-before规则?
当一个变量被修饰为volatile后,对其的读写就会显得比较特别
1.写一个volatile变量时,JMM首先修改工作内存中的变量值,并刷新到主内存中
如图所示
2.读一个变量时,JMM会把该线程对应的本地内存置为无效,并从主内存中读取共享变量。
如图所示
对volatile变量的读写,可以说都是直接对主内存进行的操作,这样虽然会牺牲一些性能,但是解决了“缓存一致性问题”,使得变量的在多线程间的可见行得到了很好的保证。
为了优化程序性能,编译器和处理器会对java编译后的字节码和机器指令进行重排序,通俗的说代码的执行顺序和我们在程序中定义的顺序会有些不同,只要不改变单线程环境下的执行结果就行。但是在多线程环境下,这么做却可能出现并发问题。比如下面的例子。
运行这段代码我们可能会得到一个匪夷所思的结果:我们获得的单例对象是未初始化的。为什么会出现这种情况?因为指令重排。首先要明确一点,同步代码块中的代码也是能够被指令重排的。然后来看问题的关键
INSTANCE = new Singleton();
虽然在代码中只有一行,编译出的字节码指令可以用如下三行表示
if (INSTANCE == null)
那么这时候有意思的事情就发生了:虽然INSTANCE指向了一个未被初始化的对象,但是它确实不为null了,所以这个判断会返回false,之后它将return一个未被初始化的单例对象!整个过程的执行流程如下图所示
由于重排序是编译器和CPU自动进行的,那么有什么办法能禁止这种重排序操作吗?很简单,给
INSTANCE变量加个volatile关键字就行,这样编译器就会根据一定的规则禁止对volatile变量的读写操作重排序了。而编译出的字节码,也会在合适的地方插入内存屏障,比如volatile写操作之前和之后会分别插入一个StoreStore屏障和StoreLoad屏障,禁止CPU对指令的重排序越过这些屏障。
对volatile变量的读写具有原子性,但是其他操作并不一定具有原子性,一个简单的例子就是i++。由于该操作并不具有原子性,故而即使该变量被volatile修饰,多线程环境下也不能保证线程安全。
volatile是jvm提供的轻量级同步工具。被volatile修饰的共享变量在多线程环境下可以获得可见行保证。其次它还能禁止指令重排。由于对volatile的写-读与锁的释放-获取具有相同的内存语义,故某些时候可以代替锁来获得更好的性能。但是和锁不一样,它不能保证任何时候都是线程安全的。
轻量级的同步机制——volatile语义详解(可见性保证+禁止指令重排)
标签:关系 相同 ring 防止 bool override instance lse 返回
原文地址:https://www.cnblogs.com/takumicx/p/9302398.html