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

发布时间:2019-11-19 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Java多线程环境下对象安全的构建方式脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

在上一篇文章《从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引用变量本身仍然需要其他机制保证可以对其它线程可见。

脚本宝典总结

以上是脚本宝典为你收集整理的Java多线程环境下对象安全的构建方式全部内容,希望文章能够帮你解决Java多线程环境下对象安全的构建方式所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。