Java的安全初始化
来源:互联网 发布:淘宝溢价是什么意思 编辑:程序博客网 时间:2024/05/17 22:01
不安全的发布
错误的延迟初始化将导致不正确的发布,如下面的程序所示。初看起来,在程序中存在的问题只有竞态条件问题。在某些特定条件下,例如当Resource的所有实例都相同时,你或许会忽略这些问题(以及在多次创建Resource实例时存在的低效率问题)。然而,即使不考虑这些问题,UnsafeLazyInitialization仍然是不安全的,因为另一个线程可能看到对部分构造的Resource实例的引用。
public classUnsafeLazyInitialization { private static Resource resource; public static ResourcegetInstance() { if(resource == null) { resource = new Resource(); } return resource; }}
假设线程A是第一个调用getInstance的线程,它将看到resource为null,并且初始化一个新的Resource,然后将resource设置为这个新实例。当线程B随后调用getInstance,它可能看到resource的值为非空,因此使用这个已经构造好的Resource。最初这看不出任何问题,但线程A写入resouce的操作与线程B读取resource的操作之间不存在Happens-Before关系。在发布对象时存在数据竞争问题,因此B并不一定能看到Resource的正确状态。
当新分配一个Resource时,Resource的构造函数将把新实例中的各个域由默认值修改为它们的初始值。由于在两个线程中都没有使用同步,因此线程B看到的线程A中的操作顺序,可能与线程A执行这些操作时的顺序并不相同。因此,即使线程A初始化Resource实例之后再将resource设置为指向它,线程B仍可能看到对resource的写入操作将在对Resource各个域的写入操作之前发生。因此,线程B就可能看到一个不部分构造的Resource实例。
注:除了不可变对象外,使用被另一个线程初始化的对象通常都是不安全的,除非对象的发布操作是在使用该对象的线程开始使用之前执行。
安全的发布
(1) 使用synchronized
public classSafeLazyInitialization { private static Resource resource; public synchronized static ResourcegetInstance() { if(resource == null) { resource = new Resource(); } return resource; }}
(2) 静态初始化器
在初始化器中采用了特殊的方式来处理静态域(或者在静态初始化代码块中初始化的值),并提供了额外的线程安全性保证。静态初始化器是由JVM在类的初始化阶段执行,即在类被加载后并且被线程使用之前。由于JVM将在初始化期间获得一个锁,并且每个线程都至少获取一次这个锁以确保这个类已经加载,因此在静态初始化期间,内存写入操作将自动对所有线程可见。因此无论是在被构造期间还是被引用时,静态初始化对象都不需要显示的同步。然而,这个规则仅适用于在构造时的状态,如果对象时可变的,那么在读线程和写线程之间,仍然需要通过同步来确保随后的修改操作是可见的,以及避免数据的破坏。
如下代码,通过使用提前初始化,避免了每次调用safeLazyInitialization中的getInstance时所产生的同步开销。通过将这项技术和JVM的延迟加载机制结合起来,可以形成一种延迟初始化技术,从而在常见的代码中不需要同步。
public classEagerInitialization{ private static Resource resource = new Resource(); public synchronized static ResourcegetResource() { return resource; }}
(3) 延迟初始化占位
使用一个专门的类来初始化Resource。JVM将推迟ResourceHolder的初始化操作,直到开始使用这个类时才初始化,并且由于通过一个静态初始化来初始化Resource,因此不需要额外的同步。当任何一个线程第一次调用getResouce时,都会使ResouceHolder被加载和被初始化,此时静态初始化器将执行Resource的初始化操作。
public classResourceFactory{ private static class ResourceHolder{ private static Resource resource = new Resource(); } public synchronized static ResourcegetResource() { return ResourceHolder.resource; }}
双重检查加锁
在任何一本介绍并发的书中都会讨论声名狼藉的双重检查加锁(DCL)。
DCL的真正问题在于:当在没有同步的情况下读取一个共享对象时,可能发生的最糟糕的事情只是看到一个失效值(在这种情况下是一个空值),此时DCL方法将通过在持有锁的情况下在此尝试来避免这种风险。然而,实际情况远比这种情况糟糕——线程可能看到引用的当前值,但对象的状态值却是失效的,这意味着线程可以看到对象处于无效或错误的状态。
public classDoubleCheckedLocking { private static Resource resource; public static ResourcegetInstatnce() { if(resource == null) { synchronized(DoubleCheckedLocking.class) { if(resource == null) { resource = new Resource(); } } } return resource; }}
在JVM的后续版本(Java5.0以及更高的版本)中,如果把resource声明为volatile类型,那么就能启用DCL。DCL方法已经被广泛地废弃了,延迟初始化占位类模式能带来同样的优势,并且更容易理解。
- Java的安全初始化
- java如何安全的延迟初始化
- java安全初始化
- 线程安全的延迟初始化
- ActiveX的初始化和脚本安全
- ActiveX的初始化和脚本安全
- 线程安全的延迟初始化方式
- 安全的初始化 mysql线程链接
- Java初始化的顺序
- 关于java的初始化
- JAVA变量的初始化
- java 类的初始化
- JAVA的初始化顺序
- Java的初始化函数
- Java的初始化
- JAVA的初始化顺序
- java 类的初始化
- java 的初始化顺序
- The specified child already has a parent. You must call removeView() on the child's parent first问题
- ifstream读取文件和stringstream读取内容
- 树的存储结构
- hdu1242 Rescue BFS+优先队列
- 选票统计
- Java的安全初始化
- python 调用 有道api接口
- leecode 解题总结:342. Power of Four
- 为什么(i++)不能做左值,而(++i)可以
- ACM-最难的问题(字符串处理)
- 【L3_002】堆栈【线段树】
- php函数microtime
- JasperReport6.3.1官网下载
- 记第一次面试