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关键字解决的是变量读时的可见性问题,但无法保证原子性,对于多线程访问同一个实例变量

还是需要加锁同步。

原文地址:https://www.cnblogs.com/edison20161121/p/8011212.html