手记

volatile 可以保证可见性,但不能保证原子性。某种意义上是线程不安全的

被volatile修饰的变量可以保证该变量的可见性,但并不能保证操作的原子性。

当需要使用被volatile修饰的变量时,线程会从主内存中重新获取该变量的值,但当该线程修改完该变量的值写入主内存的时候,并没有判断主内存内该变量是否已经变化,故可能出现非预期的结果。如主内存内有被volatile修饰变量 a,值为3,某线程使用该变量时,重新从主存内读取该变量的值,为3,然后对其进行+1操作,此时该线程内a变量的副本值为4。但此时该线程的时间片时间到了,等该线程再次获得时间片的时候,主存内a的值已经是另外的值,如5,但是该线程并不知道,该线程继续完成其未完成的工作,将线程内的a副本的值4写入主存,这时,主存内a的值就是4了。这样,之前修改a的值为5的操作就相当于没有发生了,a的值出现了意料之外的结果。

被synchronize修饰的变量则可以保证变量操作的原子性,因为当某线程使用变量a时,其他线程无法使用变量a,只能等该线程对a操作结束,释放a的锁后才能对a进行操作。

public class MyThread extends Thread {

    volatile public static int count;

    private static void addCount() {

        for (int i = 0; i < 200; i++) {

            count=i;

        }

       System.out.println("count=" + count);

   }

    @Override

    public void run() {

        addCount();

    }

}

package volatiletest;

public class Run {

    public static void main(String[] args) {

        MyThread[] mythreadArray = new MyThread[100];

        for (int i = 0; i < 100; i++) {

            mythreadArray[i] = new MyThread();

        }

        for (int i = 0; i < 100; i++) {

            mythreadArray[i].start();

        }

    }

}

结果:

count=199

count=199

count=83

count=199

count=199

在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,

线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存

变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图

描述这写交互

read and load 从主存复制变量到当前工作内存
use and assign  执行代码,改变共享变量值 
store and write 用工作内存数据刷新主存相关内容

其中use and assign 可以多次出现

但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样

对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的

例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值

在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6

线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6

导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。


1人推荐
随时随地看视频
慕课网APP