volatile和synchronize虽然目前业务层关注的比较少,但是他们是内存模型里面比较特殊的两个关键字,我们可以利用他们来了解一下内存模型里是如何解决多线程问题的。
指令重排
计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排 。
原子性
原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。
可见性
可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值。
volatile实现了禁止指令重排和可见性,并不具备原子性,严格说就是volatile对象的值在多线程情况下并不可靠,比如,一个共享变量用来做计数器,每次一个线程更新后,另外一个线程能够显式的拿到最新的值,但是在写入时,并不具备原子性,不能保证和其他写入线程并发时的唯一性。
volatile可见性和禁止指令重排实现
当写一个volatile变量时,JMM会把该线程对应的工作内存中的共享变量值刷新到主内存中,当读取一个volatile变量时,JMM会把该线程对应的工作内存置为无效,那么该线程将只能从主内存中重新读取共享变量。volatile变量正是通过这种写-读方式实现对其他线程可见( 但其内存语义实现则是通过内存屏障 )。
内存屏障,又称内存栅栏,是一个CPU指令,它的作用有两个,一是保证特定操作的执行顺序,二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。Memory Barrier的另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。总之,volatile变量正是通过内存屏障实现其在内存中的语义,即可见性和禁止重排优化。
误解:volatile变量对所有线程是立即可见的,所以对volatile变量的所有修改(写操作)都立刻能反应到其他线程中。或者换句话说:volatile变量在各个线程中是一致的,所以基于volatile变量的运算在并发下是线程安全的。
非原子性意味着写并发是有可能的
疑问:为什么我们写代码不用可以去写volatile和synchronize来确保代码的执行顺序、可见性、原子性?
如果Java内存模型中所有的有序性都要依靠volatile和synchronized来实现,那是不是非常繁琐。Java语言中有一个“先行发生原则”,是判断数据是否存在竞争、线程是否安全的主要依据。
synchronize的原子性通过加锁来实现(monitorenter和monitorexit指令),加锁时清空工作内存中共享变量的值,从而使共享变量时需要从主内存中重新获取最新的值,解锁时把共享变量的最新值刷新到主内存中,从而实现了synchronize的可见性。
synchronize修饰的代码,在同一时间只允许一个线程来执行,也就是单线程执行,也就不存在多线程下的有序性,从另外一个角度解决多多线程执行的有序性。
转载请注明:迷路的老鼠 » volatile和synchronize