资讯专栏INFORMATION COLUMN

Java多线程环境下对象安全的构建方式

xuexiangjys / 3328人阅读

摘要:在构建一个对象的过程中,更要考虑到多线程间共享数据的一致性问题,否则很可能会发生一个在线程中构建完整的对象,在线程中看到的却只被构建了一部分。例如下面的代码上面的代码本意是想实现一个单例模式,但在多线程环境下,这个单例模式将很容易被打破。

在上一篇文章《从Java多线程可见性谈Happens-Before原则》中,我们详细讨论了在并发编程中Happens-Before原则对多线程共享变量的重要性。要想确保让一个线程对共享变量的修改能被其它线程感知到,就必须让两个线程中的操作满足Happens-Before原则。
在构建一个对象的过程中,更要考虑到多线程间共享数据的一致性问题,否则很可能会发生一个在A线程中构建完整的对象,在B线程中看到的却只被构建了一部分。例如下面的代码:

public class UnsafeLazyInitialization {
    private static Resource resource;
    
    public static Resource getInstance() {
        if(resource == null) {
            resource = new Resource();
        }
        return resource;
    }
}

上面的代码本意是想实现一个单例模式,但在多线程环境下,这个单例模式将很容易被打破。
首先,resource是一个普通变量,当一个线程更新resource变量的值时,其它线程可能无法感知到resource引用已经指向了一个创建好的对象,所以可能会导致程序创建多个Resource对象。
更糟糕的是重排序,在线程A中是先初始化Resource对象的各个field之后再将resource引用设置为指向它,线程B看到的可能是对引用变量resource的写入操作在对Resource对象各个field的写入操作之前发生。这会导致线程B看到的是一个被部分构造的Resource对象实例,该对象可能处于无效状态。
为了解决上面的问题,我们可以套用Happens-Before规则。例如在getInstance方法上声明synchronized。
然而对于构建一个对象实例的操作,除了可以使用Happens-Before原则外,我们利用下面两种方式也可以保证对象的构建状态被正确发布到其它线程。

静态初始化器

静态初始化器是由JVM在的初始化阶段执行,即在类被加载后并且被线程使用前。在静态初始化期间,内存写入操作将自动对所有线程可见。所以上面的程序改下成如下代码将会确保Resource对象被正确发布到其它线程:

public class EagerInitialization {
    private static Resource resource = new Resource();
    
    public static Resource getInstance() {
        return resource;
    }
}
含有final域的对象

对于含有final域的对象,初始化安全性可以防止对对象引用的写入操作被重排序到对象构造过程之前。在构造函数完成时,构造函数对final域的所有写入操作,以及通过final域可以到达的任何变量的写入操作,都能够被获取到该对象引用的线程看到。例如下面的代码:

public class FinalInitialization {
    private static final Resource resource;
    
    public static Resource getInstance() {
        if(resource == null) {
            resource = new Resource();
        }
        return resource;
    }
}

在上面的代码中,将resource变量声明为final类型的。这样可以保证,无论哪个线程,只要获取到对象的引用的值,就一定可以看到一个被完整构建的Resource对象。
但是此处的resource引用变量本身仍然需要其他机制保证可以对其它线程可见。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67704.html

相关文章

  • 学习Java线程一些总结

    摘要:多线程环境下的一些问题安全性问题在没有正确同步的情况下,多线程环境下程序可能得出错误的结果。一些相关概念竞争条件多线程的环境下,程序执行的结果取决于线程交替执行的方式。而线程的交替操作顺序是不可预测的,如此程序执行的结果也是不可预测的。 入口 Java多线程的应用复杂性之如jvm有限的几个内存方面的操作和规范,就像无数纷繁复杂的应用逻辑建立在有限的指令集上。 如何写出线程安全的程序,有...

    coolpail 评论0 收藏0
  • 线程安全

    摘要:不可变在中,不可变的对象一定是线程安全的。在里标注自己是线程安全的类,大多都不是绝对线程安全,比如某些情况下类在调用端也需要额外的同步措施。无同步方案要保证线程安全,不一定就得需要数据的同步,两者没有因果关系。 在之前学习编程的时候,有一个概念根深蒂固,即程序=算法+数据结构。数据代表问题空间中的客体,代码就用来处理这些数据,这种思维是站在计算机的角度去抽象问题和解决问题,称之为面向过...

    fuyi501 评论0 收藏0
  • Java集合总结

    摘要:概述集合类主要有大分支,及。不能保证元素的排列顺序,顺序有可能发生变化不是同步的集合元素可以是但只能放入一个是接口的唯一实现类,可以确保集合元素处于排序状态。如果这两个的通过比较返回,新添加的将覆盖集合中原有的,但不会覆盖。 概述 Java集合类主要有2大分支,Collection及Map。Collection体系如下: https://upload-images.jianshu......

    toddmark 评论0 收藏0
  • 想进大厂?50个线程面试题,你会少?(一)

    摘要:下面是线程相关的热门面试题,你可以用它来好好准备面试。线程安全问题都是由全局变量及静态变量引起的。持有自旋锁的线程在之前应该释放自旋锁以便其它线程可以获得自旋锁。 最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案。 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员...

    wow_worktile 评论0 收藏0
  • java入门

    摘要:包含了支持服务开发的类,并为提供基础,如语言基础操作操作网络通信以及多线程等技术。在运行文件时,的解释器对这些字节码进行解释执行,执行过程中需要加入的类在连接阶段被载入到运行环境中。支持多个线程同时执行,并提供多线程之间的同步机制。 1.什么是Java语言 简单地说,Java 是由 Sun Microsystems 公司于 1995 年推出的一门面向对象程序设计语言。2009 年 Or...

    zhangfaliang 评论0 收藏0

发表评论

0条评论

xuexiangjys

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<