关于Java多线程编程学习笔记之volatile

Java内存模型

  java中的堆内存是用来存储实例化的对象,它在虚拟机启动的时候创建,在Java虚拟机规范中规定堆内存是所有对象实例以及数组都在堆内存中进行分配内存。堆内存是被所有的线程共享的内存区域,因此存在内存可见性的问题,但是局部变量,方法定义的参数则不会在线程之间共享,他们不存在内存可见性的问题,也不受Java内存模型的影响。Java内存模型定义了线程和主存之间的抽象关系。线程之间的共享变量存储在主存中,每个线程都有一个私有的本地内存。该本地内存中存储了该线程共享变量的副本。这里有一点需要注意的是本地内存是Java内存模型中一个抽象概念,它并不是真实存在的,它涵盖了缓存,写缓冲区,寄存器等区域。Java内存模型控制线程之间的通信,它决定一个线程对主存共享变量的写入何时对其他的线程可见。

  线程A和线程B之间通信必须经过下面两个步骤

  1. 线程A把线程A本地内存中更新过的变量刷新到主存中。
  2. 线程B从主存中读取线程A以及更新过的变量。


    内存模型

举个例子

int temp=10;

  如果temp 是一个线程共享变量,那么假如线程A对temp变量修改为2,那么第一步是线程A对在线程A本地内存中的temp变量的缓存进行赋值操作,然后再写入到主存中,不是直接将数字2写入到主存去的。

原子性 可见性 有序性

  在多进程(线程)访问共享资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。原子操作(atomic operation)是不需要synchronized,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。通常所说的原子操作包括对非long和double型的数据类型进行赋值,以及返回这两者之外的数据类型。之所以要把它们排除在外是因为它们都比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作(JVM可以试着去这么作,但并不保证)。
首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性

  简单的来说就是对基本数据类型的读取和赋值操作是原子性操作,也即是说这些操作是不可以中断的,要不是执行完毕,要不就是不执行,不会出现执行一半,停止,中断这种情况。下面举个例子。

x=10;  //语句1
y=x;   //语句2
x++;   //语句3

上面三条语句其中只有语句1是原子性操作,其中语句2和语句3都不是原子性操作。为什么呢?因为虽然他们都是一条语句,但是其中语句2执行的操作首先是取出X的值,然后将x的值写入工作内存中赋值给y。这个两个操作如果单独拿出来都是原子性操作。语句3 包含了三个操作,读取X的值,然后对X的值进行+1,然后再向工作内存中写入新的值。

可见性

  可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存,所以对其他线程是可见的。当有其他线程需要读取该值时,其他线程会去主存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,并不会立即写入主存中,什么时候写入主存也是不确定的,当其它线程去读取该值的时候,这个时候主存中的值仍然是原来的值,这样就无法保证可见性。

有序性

  Java内存模型中允许编译器和处理器对指令进行重排序,虽然重排序过程不会影响单线程执行的正确性,但是会影响多线程并发执行的正确性,我们可以通过volatile来保证有序性。也可以通过synchronized来保证有序性。synchronized可以保证每个时刻只有一个线程执行同步代码。这相当于是让线程执行同步代码,从而保证有序性。当一个共享变量被volatile修饰之后,其就具备了两个含义,一个是线程修改了变量的值时,变量的新值对其他线程是立即可见的。换句话说,就是不同线程对这个变量进行操作时具有可见性。另一个含义是禁止使用指令重排序 什么是重排序呢?重排序通常是编译器或者运行时环境为了优化程序性能而采取的对指令进行重排序执行的一种手段。重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境。

  volatile关键字能禁止指令重排序,因此volatile能保证有序性。volatile关键字禁止指令重排序有两个含义:一个是当程序执行到volatile变量的操作时,在其前面的操作已经全部执行完毕,并且结果会对后面的操作可见,在其后面的操作还没有进行;在进行指令优化时,在volatile变量之前的语句不能在volatile变量后面执行;同样,在volatile变量之后的语句也不能在volatile变量前面执行。
我们平时在开发中经常会使用到单例模式,很多人喜欢用double check 模式

public class Instance {

    public static Instance instance;

    private Instance() {
    }

    public static Instance getInstance() {
        if (instance == null) {
            synchronized (Instance.class) {
                if (instance == null) {
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}

  我们这样写的单例模式其实是有问题的,并不能真正的实现单例模式,假如线程A首先进入getInstance()方法,发现instance 对象为空,那么首先获取锁,这个时候如果线程B也执行getInstance()方法,发现instance对象仍然为空,那么线程B将会尝试获取锁对象,发现锁被线程A持有,这个时候线程B进入阻塞状态。在线程A完成了初始化的时候,释放锁对象,这个时候线程B将会获取锁对象,前面我说过了Java中的内存模型,线程A初始化的instance对象首先是在线程A的本地内存中,这个时候如果线程B在进行第二次判断instance对象是否为空的时候发现线程B和主内存中的instance对象为空,那么线程B也会进行初始化操作。所以这样的double check 模式并不能保证单例模式。正确的写法因该是下面这种写法

public class Instance {
    public static volatile Instance instance;
    private Instance() {
    }
    
    public static Instance getInstance() {
        if (instance == null) {
            synchronized (Instance.class) {
                if (instance == null) {
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}

  通过给instance 变量 加上volatile声明过后,一旦线程A初始化成功Instance类的对象过后,会马上把对象从线程A本地内存中刷入主内存中,并且对线程B可见,这样就避免了线程B又初始化一次对象。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 转载 原文地址:http://www.cnblogs.com/dolphin0520/p/3920373.html...
    Q先生_888阅读 259评论 0 0
  • 今天把昨天的作业补上,有些事情真的不能拖,谁也不知道明天有什么突发的事情,我周六很晚才回家,也很累就没有写,可是今...
    你好222阅读 267评论 0 0
  • 题记:从前不知时光易老,青春易逝。现在终于明白,亲人不待,情缘亦散... 1.天人永隔,珍惜当下 不知不觉间,爷爷...
    北辰星海阅读 536评论 3 7
  • 一,感恩 1.感恩游乐场建的很讲究,笑笑在里面玩的特别开心,让我在在里面玩的很开心; 2.感恩医院里面的医生给阿姨...
    农夫那阅读 158评论 0 0
  • 我的坦桑尼亚之行 嗯...从何说起呢,之所以选择坦桑尼亚有两方面原因,第一是当时match的时候有点晚了所以一直心...
    superclaire阅读 246评论 0 0