`

单例设计模式-并发

阅读更多
    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的类加载特性就必须保证类加载一定正确,否则此问题将比并发和性能更严重。如果我们的类需要初始话那么就需要想其它办法避免在构造函数中完成。看起来像是又回到了老地方,难道不是么?

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

相关推荐

    SatanDaddy#myblog#单例设计模式1

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

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

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

    最新JAVA架构师技术内幕!从亿万级业务处理到大型互联网高并发设计课程 提升必备

    ├─11.20 单例设计模式和Spring家族介绍-1.mp4 ├─11.20 单例设计模式和Spring家族介绍-2.mp4 ├─11.20 单例设计模式和Spring家族介绍-3.mp4 ├─11.22 手写Spring IOC模块.mp4 ├─11.25 手写spring ioc模块及...

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

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

    单例模式详解.txt

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

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

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

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

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

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

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

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

    Java代码积累:并发 设计模式 数据结构 使用容器 实用 类 基础知识 并发性 演示线程的生命周期 生产者-消费者 设计模式参考《大话设计模式》 工厂简单模式 创造型模式 工厂方法模式 抽象工厂模式 原型模式 建造者...

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

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

    mohuishou.github.io:Mohuishou的博客

    Go设计模式01-单例模式 Go设计模式02-工厂模式&DI容器 Go设计模式03-建造者模式 Go设计模式04-原型模式 Go设计模式05-创建型模式总结 结构型 Go设计模式06-代理模式(generate实现类似动态代理) Go设计模式07-桥接...

    23种设计模式之单例模式

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

    java并发编程

    第43节Future设计模式实现(实现类似于JDK提供的Future)00:19:20分钟 | 第44节Future源码解读00:29:22分钟 | 第45节Fork/Join框架详解00:28:09分钟 | 第46节同步容器与并发容器00:18:44分钟 | 第47节并发容器...

    实战Java高并发程序设计(第2版)PPT模板.pptx

    5并行模式与算法 5.1探讨单例模式 5.3生产者-消费者模式 5.5future模式 5.2不变模式 5.4高性能的生产者-消费者模式:无锁的实现 5.6并行流水线 01 02 03 04 05 06 实战Java高并发程序设计(第2版)PPT模板全文共25...

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

    单例模式是所有设计模式中比较简单的一类,其定义如下:Ensure a class has only one instance, and provide a global point of access to it.(保证某一个类只有一个实例,而且在全局只有一个访问点) 单例模式的...

    【C++项目设计】高并发内存池.zip

    该项目主要涉及C/C++、数据结构(链表、哈希桶)、操作系统内存管理、单例模式、多线程、互斥锁等方面的技术。 项目详解:https://blog.csdn.net/chenlong_cxy/article/details/122819562?spm=1001.2014.3001.5502

    Java并发编程原理与实战

    Future设计模式实现(实现类似于JDK提供的Future).mp4 Future源码解读.mp4 ForkJoin框架详解.mp4 同步容器与并发容器.mp4 并发容器CopyOnWriteArrayList原理与使用.mp4 并发容器ConcurrentLinkedQueue原理与使用....

    龙果java并发编程完整视频

    第43节Future设计模式实现(实现类似于JDK提供的Future)00:19:20分钟 | 第44节Future源码解读00:29:22分钟 | 第45节Fork/Join框架详解00:28:09分钟 | 第46节同步容器与并发容器00:18:44分钟 | 第47节并发容器...

    Java 并发编程原理与实战视频

    第43节Future设计模式实现(实现类似于JDK提供的Future)00:19:20分钟 | 第44节Future源码解读00:29:22分钟 | 第45节Fork/Join框架详解00:28:09分钟 | 第46节同步容器与并发容器00:18:44分钟 | 第47节并发容器...

Global site tag (gtag.js) - Google Analytics