`

单例设计模式-并发

阅读更多
    http://fanmingchao111-163-com.iteye.com/blog/984836
单例模式完全解析
本文将探讨单例模式的各种情况,并给出相应的建议。单例模式应该是设计模式中比较简单的一个,但是在多线程并发的环境下使用却是不那么简单了。
首先看最原始的单例模式。
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的类加载特性就必须保证类加载一定正确,否则此问题将比并发和性能更严重。如果我们的类需要初始话那么就需要想其它办法避免在构造函数中完成。看起来像是又回到了老地方,难道不是么?

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

相关推荐

    java单例设计模式-饿汉式-懒汉式.docx

    Java中的单例设计模式是一种常用的设计模式,它确保一个类只有一个实例,并且提供一个全局访问点。单例模式常用于管理资源,如配置信息的读取,或者在系统中需要一个全局协调者的情况下。 单例设计模式有两大关键点...

    java单例设计模式-饿汉式-懒汉式.pdf

    Java 单例设计模式主要分为两种实现方式:饿汉式和懒汉式。这两种方式都是为了确保一个类只有一个实例,并且提供全局访问点。 **饿汉式**: 饿汉式单例在类加载时就完成了实例化,因此是静态常量的方式,确保了线程...

    java单例设计模式-饿汉式-懒汉式 (2).docx

    单例设计模式是一种常用的设计模式,其主要目的是确保一个类只有一个实例,并且提供一个全局访问点。这种模式在Java中有两种常见的实现方式:饿汉式和懒汉式。 1. 饿汉式单例: 饿汉式单例在类加载时就完成了初始化...

    单例模式----数据库连接池管理类的应用

    单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在IT行业中,尤其是在处理资源密集型任务如数据库连接时,单例模式被广泛应用。数据库连接池就是这种应用的一个典型例子。 ...

    Qt单例设计模式(1-8)

    在Qt框架中,单例设计模式是一种经常被用到的设计模式,它确保一个类只有一个实例,并提供全局访问点。这个模式在管理共享资源、配置文件或者数据库连接等方面特别有用。本系列教程将深入探讨Qt中的单例设计模式,...

    单例设计模式的优缺点和设计思想

    单例设计模式是一种在软件工程中广泛使用的创建型设计模式,其核心思想是确保一个类仅有一个实例,并提供一个全局访问点。这种模式在多种场景下具有显著的优势,同时也存在一定的局限性和潜在的问题。 ### 单例设计...

    单例模式案例-打印机案例.zip

    单例模式是软件设计模式中的一种,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。在C++编程中,实现单例模式通常是为了控制资源的共享,比如我们的例子中的"打印机案例"。打印机作为一个有限的硬件...

    java单例设计模式的好处

    单例设计模式是软件开发中一种重要的设计模式,它的核心思想是确保一个类只有一个实例,并提供全局访问点。在Java中,单例模式通常用于控制特定类的实例化过程,以达到节省系统资源、控制并发访问和实现数据共享等...

    1.设计模式-单例设计模式1

    总的来说,单例设计模式在节省资源、控制并发和简化代码结构等方面有着重要的作用。理解并正确实现单例模式,能够帮助我们编写更加高效和易于维护的代码。同时,也需要关注其可能带来的问题,如序列化和反射攻击,...

    SatanDaddy#myblog#单例设计模式1

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

    C++单例设计模式

    单例设计模式是一种重要的软件设计模式,用于确保一个类只有一个实例,并提供全局访问点。这种模式在C++中尤其常见,因为它允许多个组件共享同一对象,从而提高效率和一致性。以下是对C++单例模式的详细说明: 1. *...

     单例设计模式Singleton1

    单例设计模式Singleton1 单例设计模式Singleton1是Java设计模式中的一种创建型模式,指某个类采用Singleton模式,则在这个类被创建后,只可能产生一个实例供外部访问,并且提供一个全局的访问点。这个模式的核心...

    设计模式-Java单例模式的各种实现实例

    单例模式(Singleton Pattern)是一种常用的软件设计模式,它保证一个类仅有一个实例,并提供一个访问该实例的全局访问点。这种模式通常用于需要频繁地创建和销毁的对象,以减少系统性能开销。 单例模式的优点: ...

    2 单例模式-MOOC课程内容.pdf

    单例模式是软件设计模式中的一种,属于创建型模式,但有时被称作“非创建型模式”。在单例模式中,设计者的目标是确保一个类只有一个实例,并且提供一个全局访问点给这个实例。在多线程和并发环境中,单例模式的实现...

    常见设计模式-单例模式

    单例模式是设计模式中的一种,其主要目的是确保一个类只有一个实例,并且提供一个全局访问点来获取这个实例。在Java编程中,实现单例模式有多种方式,每种方式都有其优缺点。 1. **饿汉式**: 饿汉式在类加载时即...

    IOS 单例设计模式实例Demo

    在iOS开发中,单例(Singleton)设计模式是一种常见的编程模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统中需要频繁创建和销毁对象,或者需要共享资源的情况下尤其有用。本示例Demo将深入...

    java 单例设计模式

    Java中的单例设计模式是一种创建型设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于管理共享资源,如数据库连接、线程池或配置对象。单例模式的关键在于限制类的实例化,防止多个实例的...

    多线程单例模式并发访问

    ### 多线程单例模式并发访问 #### 一、多线程基础概念 在讨论多线程单例模式及并发访问之前,我们先来了解一些基本概念。 **进程**和**线程**是计算机科学中的两个核心概念,它们之间的关系紧密而复杂。 - **进程...

    多线程(20)单例设计模式1

    单例设计模式是一种常见且重要的软件设计模式,其主要目标是确保一个类只有一个实例,并提供一个全局访问点。在多线程环境中,单例模式尤其关键,因为它可以保证多个线程安全地共享同一实例,避免资源浪费和并发问题...

    C++设计模式-工厂和单例

    本篇将深入探讨两种重要的设计模式:工厂模式和单例模式,以及如何在VC(Visual C++)环境下实现它们。 ### 工厂模式 **定义**:工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式。在工厂模式中,当...

Global site tag (gtag.js) - Google Analytics