2.3.4volatile的原子性
关键字volatile虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也不具备原子性。
测试
package com.cky.thread; /** * Created by edison on 2017/12/9. */ public class MyThread extends Thread{ volatile public static int count; private static void addCount() { for(int i=0;i<100;i++) { count++; } System.out.println("count="+count); } @Override public void run() { super.run(); addCount(); } }
package com.cky.test; import com.cky.thread.MyThread; /** * Created by edison on 2017/12/9. */ public class Run { public static void main(String[] args) { MyThread[] myThreads = new MyThread[100]; for (int i = 0; i < 100; i++) { myThreads[i] = new MyThread(); } for (int i = 0; i < 100; i++) { myThreads[i].start(); } } }
count=8600 count=8500 count=8400 count=8200 count=8300 count=8000 count=7800 count=7700 count=7500
结果:线程异步
更改加上关键字synchronized
package com.cky.thread; /** * Created by edison on 2017/12/9. */ public class MyThread extends Thread{ volatile public static int count; synchronized private static void addCount() { for(int i=0;i<100;i++) { count++; } System.out.println("count="+count); } @Override public void run() { super.run(); addCount(); } }
count=9400 count=9500 count=9600 count=9700 count=9800 count=9900 count=10000
在本例中,如果在方法private static void addCount()前加入synchronized同步关键字,也就没有必要再使用volatile关键字来声明count变量了。
关键字volatile主要使用的场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值的使用,也就是多线程读取共享变量时可以获得最新值的使用。
关键字volatile提示线程每次从共享变量中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性,但在这里需要注意的是
如果修改实例变量中的数据,例如i++,也就是i=i+1,则这样的操作其实不是一个原子操作,也就是非线程安全,表达式i++操作步骤
1)从内存中取出i值
2)计算i值
3)将i值写到内存中
假设在第2步计算值的时候,另外一个线程也修改i值,那么这个时候会出现脏读,解决方法就是使用synchronized关键字,所以volatile本身并不处理数据的
原子性,而是强制对数据的读写及时影响到主内存。
如下图演示的是volatile出现非线程安全的原因
结论:
1)read 和load阶段:从主内存复制变量到当前线程工作内存
2)use和assign阶段:执行代码,改变共享变量值
3)store和write阶段:用工作内存数据刷新主内存对应的变量的值
在多线程环境中。use和assign是多次出现的,但这个操作不是原子性,也就是在read和load之后,如果主内存中的count变量发生修饰之后,线程
工作内存中的值已经加载,不会产生对应的变化,也就是私有内存和公共内存中的值不同步,所以计算出来的结果和预期不一样,也就出现了线程不安全
对于volatitle修饰的变量,JVM只是保证从主内存加到线程工作内存中的值是最新的,例如线程1和线程2在进行read和load操作时,发现主内存中的
count值都是5,那么都会加载这个最新值,也就是说,volatile关键字解决的是变量读时的可见性问题,但无法保证原子性,对于多线程访问同一个实例变量
还是需要加锁同步。