`
小乙静流
  • 浏览: 35170 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

单例模式 并发(转)

 
阅读更多
单例模式完全解析
本文将探讨单例模式的各种情况,并给出相应的建议。单例模式应该是设计模式中比较简单的一个,但是在多线程并发的环境下使用却是不那么简单了。
首先看最原始的单例模式。
1 package xylz.study.singleton;
2
3 public class Singleton {
4
5     private static Singleton instance = null;
6
7     private Singleton() {
8     }
9
10     public static Singleton getInstance() {
11         if (instance == null) {
12             instance = new Singleton();
13         }
14         return instance;
15     }
16 }
17

显然这个写法在单线程环境下非常好,但是多线程会导致多个实例出现,这个大家都能理解。
最简单的改造方式是添加一个同步锁。
1 package xylz.study.singleton;
2
3 public class SynchronizedSingleton {
4
5     private static SynchronizedSingleton instance = null;
6
7     private SynchronizedSingleton() {
8     }
9
10     public static synchronized SynchronizedSingleton getInstance() {
11         if (instance == null) {
12             instance = new SynchronizedSingleton();
13         }
14         return instance;
15     }
16 }
17

显然上面的方法避免了并发的问题,但是由于我们只是在第一次构造对象的时候才需要同步,以后就不再需要同步,所以这里不可避免的有性能开销。于是将锁去掉采用静态的属性来解决同步锁的问题。
1 package xylz.study.singleton;
2
3 public class StaticSingleton {
4
5     private static StaticSingleton instance = new StaticSingleton();
6
7     private StaticSingleton() {
8     }
9
10     public static StaticSingleton getInstance() {
11         return instance;
12     }
13 }
14

上面的方法既没有锁又解决了性能问题,看起来已经满足需求了。但是追求“完美”的程序员想延时加载对象,希望在第一次获取的时候才构造对象,于是大家非常聪明的进行改造,也即非常出名的双重检查锁机制出来了。
1 package xylz.study.singleton;
2
3 public class DoubleLockSingleton {
4
5     private static DoubleLockSingleton instance = null;
6
7     private DoubleLockSingleton() {
8     }
9
10     public static DoubleLockSingleton getInstance() {
11         if (instance == null) {
12             synchronized (DoubleLockSingleton.class) {
13                 if (instance == null) {
14                     instance = new DoubleLockSingleton();
15                 }
16             }
17         }
18         return instance;
19     }
20 }
21

双重锁机制看起来非常巧妙的避免了上面的问题。但是真的是这样的吗?文章《双重检查锁定及单例模式》中谈到了非常多演变的双重锁机制带来的问题,包括比较难以理解的指令重排序机制等。总之就是双重检查锁机制仍然对导致错误问题而不是性能问题。

一种避免上述问题的解决方案是使用volatile关键字,此关键字保证对一个对象修改后能够立即被其它线程看到,也就是避免了指令重排序和可见性问题。参考文章

指令重排序与happens-before法则。

所以上面的写法就变成了下面的例子。

package xylz.study.singleton;

public class DoubleLockSingleton {

    private static volatile DoubleLockSingleton instance = null;

    private DoubleLockSingleton() {
    }

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


于是继续改造,某个牛人利用JVM的特性来解决上述问题,具体哪个牛人我忘记了,但是不是下面文章的作者。
(1)《Java theory and practice: Fixing the Java Memory Model, Part 2》
(2)《Initialize-On-Demand Holder Class and Singletons》

1 package xylz.study.singleton;
2
3 public class HolderSingleton {
4
5     private static class HolderSingletonHolder {
6
7         static HolderSingleton instance = new HolderSingleton();
8     }
9
10     private HolderSingleton() {
11         //maybe throw an Exception when doing something
12     }
13
14     public static HolderSingleton getInstance() {
15         return HolderSingletonHolder.instance;
16     }
17 }
18




上述代码看起来解决了上面单例模式遇到的所有问题,而且实际上工作的很好,没有什么问题。但是却有一个致命的问题,如果第11行抛出了一个异常,也就是第一次构造函数失败将导致永远无法再次得到构建对象的机会。
使用下面的代码测试下。
1 package xylz.study.singleton;
2
3 public class HolderSingletonTest {
4
5     private static class HolderSingletonHolder {
6
7         static HolderSingletonTest instance = new HolderSingletonTest();
8     }
9
10     private static boolean init = false;
11    
12     private HolderSingletonTest() {
13         //maybe throw an Exception when doing something
14         if(!init) {
15             init=true;
16             throw new RuntimeException("fail");
17         }
18     }
19
20     public static HolderSingletonTest getInstance() {
21         return HolderSingletonHolder.instance;
22     }
23     public static void main(String[] args) {
24         for(int i=0;i<3;i++) {
25             try {
26                 System.out.println(HolderSingletonTest.getInstance());
27             } catch (Exception e) {
28                 System.err.println("one->"+i);
29                 e.printStackTrace();
30             }catch(ExceptionInInitializerError err) {
31                 System.err.println("two->"+i);
32                 err.printStackTrace();
33             }catch(Throwable t) {
34                 System.err.println("three->"+i);
35                 t.printStackTrace();
36             }
37         }
38     }
39 }
40
很不幸将得到以下输出:
1 two->0
2 java.lang.ExceptionInInitializerError
3     at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
4     at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
5 Caused by: java.lang.RuntimeException: fail
6     at xylz.study.singleton.HolderSingletonTest.<init>(HolderSingletonTest.java:16)
7     at xylz.study.singleton.HolderSingletonTest.<init>(HolderSingletonTest.java:12)
8     at xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder.<clinit>(HolderSingletonTest.java:7)
9      2 more
10 three->1
11 java.lang.NoClassDefFoundError: Could not initialize class xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
12     at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
13     at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
14 three->2
15 java.lang.NoClassDefFoundError: Could not initialize class xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
16     at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
17     at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
18

很显然我们想着第一次加载失败第二次能够加载成功,非常不幸,JVM一旦加载某个类失败将认为此类的定义有问题,将来不再加载,这样就导致我们没有机会再加载。目前看起来没有办法避免此问题。如果要使用JVM的类加载特性就必须保证类加载一定正确,否则此问题将比并发和性能更严重。如果我们的类需要初始话那么就需要想其它办法避免在构造函数中完成。看起来像是又回到了老地方,难道不是么?

总之,结论是目前没有一个十全十美的单例模式,而大多数情况下我们只需要满足我们的需求就行,没必有特意追求最“完美”解决方案。
分享到:
评论

相关推荐

    多线程单例模式并发访问

    深入浅出:讲解单例模式,多线程安全和并发访问问题.让你轻松应对面试

    多线程并发下的单例模式-源码

    该资源是多线程并发下的单例模式-源码,几乎包含了所有方式实现的单例模式,并且能够确保在多线程并发下的线程安全性。 读者可结合本人博客 http://blog.csdn.net/cselmu9?viewmode=list 中的《线程并发之单例模式...

    设计模式之单例模式和工厂模式

    细心整合和单例模式和工厂模式的几种模型,懒汉式,饿汉式,如何并发操作模式,等都有详细讲解

    高并发下多种单例模式实现源码

    单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。一般用于全局接口(比如用于全局信息配置)。他是非常重要的,也最广泛的。同时跟多线程有关...

    单例模式详解.txt

    单例模式的实现机制,并发情况下的单例模式的存在问题及解决方法,无锁的线程安全单例模式

    单例模式简单实现

    实现单例模式:1、私有化的构造函数。2、提供静态方法或者公共属性提供全局访问点。3、考虑多线程高并发的问题。 注意点:lock之前判断一下实例是否为空。lock之前判断一下什么情况可以lock什么情况不可以,一般多...

    最全的单例模式j(java实现),下到就是赚到!

    最近在学习多线程相关知识,同时加深了对单例的理解,从并发的角度学习到了不同的单列模式,提供出来供大家一起学习

    击穿单例模式的Demo示范代码

    对于设计模式,最熟知和最常用的不外乎单例模式和工厂模式,对于单例模式如果编写不严谨的话也存在安全漏洞问题,这个击穿单例模式的代码很形象的说明了这个问题,其中包含如何使用现成并发技术,欢迎大家下载学习。

    JAVA多线程并发下的单例模式应用

    单例模式应该是设计模式中比较简单的一个,也是非常常见的,但是在多线程并发的环境下使用却是不那么简单了,今天给大家分享一个我在开发过程中遇到的单例模式的应用。 首先我们先来看一下单例模式的定义: 一个类有...

    23种设计模式之单例模式

    单例模式 饿汉式: /** * 饿汉式单例(提前把对象创建) * 可能会浪费空间,提前把对象创建好了,但是不一定会用。 */ public class Hungry { private Hungry(){ } private final static Hungry HUNGRY=new ...

    [并发并行]_[线程同步]_[pthread_once 实现单例模式分析]

    http://blog.csdn.net/infoworld/article/details/49798215 mingw 编译,Win32部分可以直接用vs编译.

    你想要Android数据库操作精华(安全、并发、单例等)

    自己写的android数据库操作Demo,使用的是Android Studio开发,包括数据库框架、数据库的加密、并发,单例模式操作数据库,绝对是你想要的Demo。

    【资源免费下载】Java代码积累丨大话设计模式(Java实现版本)、线程协作

    单例模式 结构型模式 队列模式 桥接模式 组合模式 装饰模式 外观模式 享元模式 代理模式 行为模式(类行为模式) 解释器模式 模板方法模式 行为模式(对象行为模式) 策略模式 观察者模式 状态模式 导入模式 迭代器...

    SatanDaddy#myblog#单例设计模式1

    * 单例设计模式-饿汉式// 构造器* 返回实例对象* 饿汉式避免了并发安全问题,但是却无法实现lazyLoad饿汉式面临的问题:对象无法实现lazy-load

    覆盖一系列高级主题,包括复杂的语法和特性、Python的高级编程技巧、常见的设计模式、并发编程、性能优化等

    设计模式: 探讨常见的设计模式,如工厂模式、单例模式、观察者模式等,以及如何在Python中应用这些模式。 测试和调试: 介绍高级的测试技术和调试工具,以确保代码的质量和可维护性。 性能优化: 提供关于Python...

    毕业设计订餐系统源码-design_pattern:设计模式

    单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; 单例模式在某种情况下会导致“资源瓶颈”。 单例模式的应用举例: 生成全局惟一的序列号; 访问全局复用的惟一资源,如磁盘、总线

    高性能高并发服务器架构

    1.不要频繁的new对象,对于在整个应用中只需要存在一个实例的类使用单例模式.对于String的连接操作,使用StringBuffer或者StringBuilder.对于utility类型的类通过静态方法来访问。 2. 避免使用错误的方式,如Exception...

    基于Linux的web服务器

    项目是在Linux下以C++开发语言搭建的Web服务器,服务器可以支持相对数量的客户端并发和及时响应,该项目支持用户注册、登录,访问图片和视频。...使用单例模式和RAII机制实现数据库连接池,减少数据库连接的开销

    java经典面试题目-面经-java-Java语言的进阶概念-常用的库和框架-并发编程-网络编程-Web开发-面经

    如何实现线程安全的单例模式? 什么是Java中的生命周期回调方法?列举一些常见的生命周期回调方法。 什么是Java中的注解处理器?如何自定义和使用注解处理器? 什么是Java中的并发编程?列举一些常见的并发类和...

Global site tag (gtag.js) - Google Analytics