java并发编程之volatile关键字解析
2017-04-27 09:06
204 查看
volatile(不稳定的,易挥发)关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情本文我们就从JVM内存模型开始,了解下volatile的应用场景。
JVM内存模型
-------------------------------------------------------------------------------------------
在了解volatile之前,我们必要对jvm的内存模型有一个基本的了解。java的内存模型规定了所有的变量都存储在主内存中(即物理硬件的内存),每条线程还具有自己的工作内存(工作内存可能位于处理器的高速缓存之中),线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间无法直接访问对方工作内存之间的变量,线程间变量值得传递需要通过主内存来完成。
对于上面提到的副本拷贝,比如假设线程中访问一个10mb的对象,并不会把这10mb的内存复制一份拷贝出来,实际上这个对象的引用,对象中某个在线程访问到的字段是有可能存在拷贝的,但不会有虚拟机实现把整个对象拷贝一次。
在并发过程中,我们通常会遇到以下三个问题:原子性,可见性,有序性,下面我们来具体看下这三个特征与volatile之间的联系。
---- ----------------------------------------------------
1.有序性
对于上面的代码我们本意是想输出20,但是如果运行的话可以发现输出的值可能会是0,。这是因为有时候为了提供程序的效率,JVM会做进行及时编译,也就是可能会对指令进行重排序,将isInited = true;放在number = 20;之前执行,在单线程下面这样做没有任何问题,但是在多线程下则会出现重排序问题。如果我们将number声明为volatile就可以很好的解决这个问题,这可以禁止JVM进行指令排序,也就意味着Number20;一定会在isInited=true前面执行。
---------------------------------------------------------------------------------------------------------------------------------
可见性
比如对于变量a,当线程一定要修改变量a的值,首先需要将a的值从主存复制过来,再将a的值加1.再将a的值复制回主存。在单线程下面,这样的操作没有任何问题,但是在多线程下面,比如还有一个线程2,在线程1在修改a的值得时候,也从主存将a的值复制过来进行加1,随后线程1和线程2先后将a的值复制回主存,但是主存中的a值最终只会加1而不是加2.
使用volatile可以解决这个问题,它可以保证在线程一修改a的值之后立即将修改值同步到主存中,这样线程2拿到的值就是线程1修改过的a的值。
3.原子性
原子性是指cpu在执行一条语句的时候,不会中途去执行另外的语句。比如i = 1就是就是一个原子操作,但是++i就不是一个原子操作了,因为它要首先读取i的值,然后修改i的值,然后将值写入主存中。
但是volatile却不能保证程序的原子性,下面我们通过一个实例来验证下:
public class TestCase { public volatile int v = 0; public static final int threadCount = 20; public void increase() { v++; } public static void main(String[] args) { TestCase testCase = new TestCase(); for (int i=0; i<threadCount; i++) { new Thread( () -> { for (int j=0; j<1000; j++) { testCase.increase(); } } ).start(); } while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println(testCase.v); }}
输出结果为 18921 上面我们的本意是想让输出20000,但是运行后结果小于20000,。因为v++它本身并不是一个原子操作,它是分为多个步骤的,而且volatile本身也并不能保证原子性。
上面的程序使用synchronzied则可以很好的解决,我们只要在非原子性操作的方法上加上同步锁,
public synchronized increase()就行了。
参考文献:http://www.ziwenxie.site/2017/04/24/java-multithread-volatile/
JVM内存模型
-------------------------------------------------------------------------------------------
在了解volatile之前,我们必要对jvm的内存模型有一个基本的了解。java的内存模型规定了所有的变量都存储在主内存中(即物理硬件的内存),每条线程还具有自己的工作内存(工作内存可能位于处理器的高速缓存之中),线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间无法直接访问对方工作内存之间的变量,线程间变量值得传递需要通过主内存来完成。
对于上面提到的副本拷贝,比如假设线程中访问一个10mb的对象,并不会把这10mb的内存复制一份拷贝出来,实际上这个对象的引用,对象中某个在线程访问到的字段是有可能存在拷贝的,但不会有虚拟机实现把整个对象拷贝一次。
在并发过程中,我们通常会遇到以下三个问题:原子性,可见性,有序性,下面我们来具体看下这三个特征与volatile之间的联系。
---- ----------------------------------------------------
1.有序性
对于上面的代码我们本意是想输出20,但是如果运行的话可以发现输出的值可能会是0,。这是因为有时候为了提供程序的效率,JVM会做进行及时编译,也就是可能会对指令进行重排序,将isInited = true;放在number = 20;之前执行,在单线程下面这样做没有任何问题,但是在多线程下则会出现重排序问题。如果我们将number声明为volatile就可以很好的解决这个问题,这可以禁止JVM进行指令排序,也就意味着Number20;一定会在isInited=true前面执行。
---------------------------------------------------------------------------------------------------------------------------------
可见性
比如对于变量a,当线程一定要修改变量a的值,首先需要将a的值从主存复制过来,再将a的值加1.再将a的值复制回主存。在单线程下面,这样的操作没有任何问题,但是在多线程下面,比如还有一个线程2,在线程1在修改a的值得时候,也从主存将a的值复制过来进行加1,随后线程1和线程2先后将a的值复制回主存,但是主存中的a值最终只会加1而不是加2.
使用volatile可以解决这个问题,它可以保证在线程一修改a的值之后立即将修改值同步到主存中,这样线程2拿到的值就是线程1修改过的a的值。
3.原子性
原子性是指cpu在执行一条语句的时候,不会中途去执行另外的语句。比如i = 1就是就是一个原子操作,但是++i就不是一个原子操作了,因为它要首先读取i的值,然后修改i的值,然后将值写入主存中。
但是volatile却不能保证程序的原子性,下面我们通过一个实例来验证下:
public class TestCase { public volatile int v = 0; public static final int threadCount = 20; public void increase() { v++; } public static void main(String[] args) { TestCase testCase = new TestCase(); for (int i=0; i<threadCount; i++) { new Thread( () -> { for (int j=0; j<1000; j++) { testCase.increase(); } } ).start(); } while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println(testCase.v); }}
输出结果为 18921 上面我们的本意是想让输出20000,但是运行后结果小于20000,。因为v++它本身并不是一个原子操作,它是分为多个步骤的,而且volatile本身也并不能保证原子性。
上面的程序使用synchronzied则可以很好的解决,我们只要在非原子性操作的方法上加上同步锁,
public synchronized increase()就行了。
参考文献:http://www.ziwenxie.site/2017/04/24/java-multithread-volatile/
相关文章推荐
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析(三)
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- volatile关键字解析——java并发编程
- volatile关键字解析——java并发编程
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析
- Java并发编程:volatile关键字解析(四.深入剖析volatile关键字)