Java内存模型 之三个特性:

Java内存模型有三个特性:原子性、可见性、有序性。

这个三个特性主要体现在多线程环境下对变量的操作。这些变量包括:实例字段、静态字段、构成数组对象的元素。这些变量都保存在堆中,
堆是线程共享的。那么这些变量在多线程环境下就有可能出现所谓“线程不安全”的问题。 另,局部变量和方法参数是线程私有的,保存在栈中,不会出现线程安全问题。

原子性:
这个概念是事务中的原子性大概一致。表明此操作是不可分割的,不可中断,要全部执行,要么全部不执行。这个特性的重点在于不可中断,举例子说明下,如下代码:

int  a=0;
int  b=0;
int  c=0;

线程A执行上述代码,从内存中读取这三个变量的值,在读取的过程中,此时线程B也读取了某一个变量的值,此时虽然线程B的这个操作并不会对线程A的结果产生影响,但是线程A的原子性已经不存在了,在底层CPU执行的时候,就会涉及到切换线程A、B。并且,对A要进行中断,所以线程A的原子性就被破坏了。理解这一点,也就会理解关键字volatile并不能保证原子性,保证原子性需要加锁。

在单例模式中,如果不是使用加锁的方法,就会因为没有保证原子性,而使得对象会被创建多个。

package com.txc.singleton;

import java.io.Serializable;

public class Singleton1 {


    private volatile static Singleton1 singleton=null;


    private  static boolean  flag = true;
    private Singleton1 (){   }

    public static Singleton1 getSingleton() {

                if (singleton == null) {
                    singleton = new Singleton1();
                }

        return singleton;
    }



}

虽然使用了关键字volatile,但是两个线程会同时执行getSingleton()方法,并且可以都通过为null的判断,然后进行对象的创建,因为getSingleton()这段代码的原子性没有被保证,保证的方式就是为这个方法加锁。

还有一个例子就是32bit的jvm操作long类型数据。Long需要64位,多线程环境下,对long类型的数据进行加运算,有可能一个线程读取的数据是另一个线程修改“一半”的数据,比如只修改了低位,还没有修改高位,此时数据被另一个线程读取,那么结果自然就是错的。解决办法就是在修改long数据的方法上加锁,保证此方法被某一线程调用时,不被其他线程干扰。
可见性:
一个线程对某一共享变量修改之后,另一个线程要立即获取到修改后的结果。那么会不会出现一个线程没有及时获取到另一个线程修改变量后的情况呢?当然有,看单例模式。

package com.txc.singleton;

import java.io.Serializable;

public class Singleton1 {



    private static Singleton1 singleton;


    private  static boolean  flag = true;
    private Singleton1 (){

    }

    public static Singleton1 getSingleton() {
        if (singleton == null) {//代码x
            synchronized (Singleton1.class) {
                if (singleton == null) {
                    singleton = new Singleton1();
                }
            }
        }
        return singleton;
    }


}

上述单例模式,单例对象singleton,没有使用volatile关键字,那么两个线程同时执行到代码x时候,此时只有一个线程获的锁,然后创建singleton实例,此时singleton的值(引用),保存在私有内存中,并不会马上刷新进主内存,而另一个线程使用的singleton的值,也是这个线程私有内存的值,并不一定会读取主内存中的singleton,也会执行后面的代码再进行创建对象。上述情况就是内存中一个变量的值,存在了不可见性,即另一个线程没有马上得到最新的变量的值。当然使用volatile可以保证可见性。

线程在使用共享变量时,首先将变量从主内存中load进私有内存,然后在私有内存中修改,修改完之后在save进主内存。可见性就是指此load和save操作应立即执行。保证可以立即执行的方法有使用volatie关键字。在单例模式的双重校检中有用到。Volatie的作用就是保证线程在使用共享变量时一定从主内存中load进私有内存,而不是直接使用私有内存中保存的副本。在修改完之后立即将此变量save进主内存。
有序性:
在单线程环境下,程序永远会“有序的”执行,即:线程内表现为串行语义。但是在多线程环境下,程序会出现“乱序”。这是由于指令重排和主内存和私有内存同步延迟造成的。在Java中使用volatile和synchronized来保证多线程之间的有序性。Volatile通过加入内存屏障指令来禁止内存的重排序。Synchronized通过加锁,让一段代码只能由一个线程来执行。这两个区别就是,synchronized可以修饰一段代码,或者一个方法。但是volatile只能修饰一个变量。

    原文作者:java内存模型
    原文地址: https://blog.csdn.net/chao_19/article/details/51661206
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞