单例模式大汇总

看了多方资料,整理下单例设计模式,有不少值得相互探究的地方,你就会发现就这一个小小的单例模式竟然映射出N多知识。我在这里把问题综述出来,一起相互探讨。 

单例涉及到的相关文章如下: 
                反射、枚举与单例 
                序列化与单例 
                类加载器与单例 

本文则主要是讲多线程与单例。 
单例模式首先分为懒汉式和饿汉式。所谓饿汉式即一开始就创建出单例对象,懒汉式则为当需要使用的时候才会去创建出单例对象。 
先看下饿汉式: 

?


1

2

3

4

5

6

7

8

9

10

public final class Singleton {

 

    private static final Singleton instance=new Singleton();

     

    private Singleton(){}

     

    public static Singleton getInstance(){

        return instance;

    }

}

1 私有化构造器,使得别人无法再创建新对象。 
问题1:即使私有化构造器,别人仍然可以通过反射机制来创建新对象,要是这样的话,下面的很多单例方法都不再是单例。然而枚举除外。 

2 这种饿汉式的方式在类加载器加载Singleton的时候就会去初始化创建一个Singleton实例,类加载器加载Singleton时线程安全的,所以这种方式不存在线程安全问题。 

懒汉式:有时候为了在使用的时候才去创建单例对象,就要采用懒汉式 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

public final class Singleton {

 

    private static Singleton instance=null;

     

    private Singleton(){}

     

    public static Singleton getInstance(){

        if(instance==null){

            instance=new Singleton();

        }

        return instance;

    }

}

这种方式即在需要的时候才会去创建单例对象。很明显,大家都知道这种方式引入了线程安全问题,所以要对getInstance方法加上锁,如下: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

public final class Singleton {

 

    private static Singleton instance=null;

     

    private Singleton(){}

     

    public static synchronized Singleton getInstance(){

        if(instance==null){

            instance=new Singleton();

        }

        return instance;

    }

}

这样的话,每个线程要执行getInstance方法时,synchronized对他们进行了同步,保证并发情况下只有一个线程在执行getInstance方法。这种做法的的确解决了线程安全问题,但是却造成了很大的性能开销。因为instance只需要在第一次创建时进行同步,创建后每次获取时不需要再进行同步,所以我们要进一步改进: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public final class Singleton {

 

    private static Singleton instance=null;

     

    private Singleton(){}

     

    public static Singleton getInstance(){

        if(instance==null){

            synchronized (Singleton.class) {

                instance=new Singleton();

            }

        }

        return instance;

    }

}

这种方式即缩小了同步的范围,保证了在单例对象创建出来后,每次获取时不需要再进行同步,但是又造成了一个问题,即不能保证instance=new Singleton()只被执行一次,所以又要改进,需要在同步的代码中再次检查是否已经创建,如下: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public final class Singleton {

 

    private static Singleton instance=null;

     

    private Singleton(){}

     

    public static Singleton getInstance(){

        if(instance==null){

            synchronized (Singleton.class) {

                if(instance==null){

                    instance=new Singleton();

                }

            }

        }

        return instance;

    }

}

这就是所谓的双重检查机制。看似已经完美,实则不然。instance=new Singleton()实际上分为三个过程: 
1 分配内存 
2 对Singleton的一些初始化工作包括构造函数的执行 
3 对instance变量赋值内存地址 
然而对于第2步和第3步,不同的编译器由于执行了优化导致他们的执行顺序并不一致,即发生了重排序,对于重排序参见这篇infoq上的文章http://www.infoq.com/cn/articles/java-memory-model-2 
也就是线程1当执行到第2步的时候,instance就已经有值了,此时线程2执行getInstance方法的最外层的if(instance==null)判断就会直接返回。然而该对象还没有真正的完成初始化,还不能正常使用。此时线程2如果去使用该对象,就会出问题了。 

为了解决这个问题,就是不允许第2步和第3步进行重排序,使用volatile来解决,如下: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public final class Singleton {

 

    private volatile static Singleton instance=null;

     

    private Singleton(){}

     

    public static  Singleton getInstance(){

        if(instance==null){

            synchronized (Singleton.class) {

                if(instance==null){

                    instance=new Singleton();

                }

            }

        }

        return instance;

    }

}

只需要在Singleton instance变量上加上volatile修饰,就可以禁止重排序。我们知道synchronized 即保证可见性又保证了互斥性,而volatile则仅仅是保持了可见性,而这里volatile又起到禁止重排序的功能(我也不懂,留给大神们去研究)。 

另一种解决方案是,基于类初始化的解决方案: 

?


1

2

3

4

5

6

7

8

9

10

11

12

public final class Singleton {

 

    private static class SingletonHolder{

        public static Singleton instance=new Singleton();

    }

     

    private Singleton(){}

     

    public static  Singleton getInstance(){

        return SingletonHolder.instance;

    }

}

这也是一种常见的懒汉式单例,接下来我们就要分析分析它是如何解决多线程问题的。 
当多个线程执行SingletonHolder.instance时,会首先进行类的初始化,即多个线程可能同时去初始化同一个类,这方面对于jvm来说是进行了细致的同步,每个类都有一个初始化锁,来确保只能有一个线程来初始化类。当线程A获取了SingletonHolder类的初始化锁,线程B则需要等待,线程A就要去执行SingletonHolder的静态变量表达式、静态代码块等初始化工作,然后就能确保Singleton instance=new Singleton()只被一个线程来执行。 
总的来说,此种方法是依靠jvm对类和接口的同步来实现单例线程安全的。具体jvm对于类和接口初始化的同步过程可以见这篇文章http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization 

时间: 2024-10-23 07:10:34

单例模式大汇总的相关文章

ANDROID内存优化(大汇总——上)

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读.(本文最后我会尽量列出所参考的文章). 内存简介: RAM(random acc

ANDROID内存优化(大汇总——中)

写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读.(本文最后我会尽量列出所参考的文章). OOM: 内存泄露可以引发很多的问题: 1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC) 2.莫名消失(当你的程序所占内存越大,它在

国外大神的机器学习算法大汇总;如何用 50 行 PyTorch 代码实现 GANs | AI开发者头条

工具推荐:基于 LMDB 的机器学习张量数据快速读写工具 今天推荐一款基于 LMDB(Lightning Memory-Mapped Database)数据库的张量读写工具,专门为加快机器学习领域的数据读取速度而设计. 详情:https://github.com/vicolab/ml-pyxis 机器学习算法大汇总 近日有国外大神祭出了一张神图,图中针对机器学习领域几乎所有的常见算法进行了分类大汇总.不但简单介绍了每一种算法的大概含义,还整理了它们的常见应用和优缺点,各位开发者绝对不可错过. 原

浏览器兼容性问题大汇总_javascript技巧

JavaScript 1.HTML对象获取问题 FireFox:document.getElementById("idName"); ie:document.idname或者document.getElementById("idName"). 解决办法:统一使用document.getElementById("idName"); 2.const问题 说明:Firefox下,可以使用const关键字或var关键字来定义常量; IE下,只能使用var

2013流行词句大汇总,不知道真就out了!!

      转眼间,2013即将画上一个完美句号,此时此刻,不禁想说一句话:"2013表走,我稀饭你,舍不得你,表走,表走!"还记得,那些陪我们一起走过的华丽丽的流行词汇吗?今年的那些标准,你还记得吗?        1.新世纪女性       上得了厅堂,下得了厨房,写得了代码,查得出异常,杀得了木马,翻得了围墙,开得起好车,买得起新房,斗得过二奶,打得过流氓!       2.六种新说法       我不叫我,叫"偶":不错不叫不错,叫"8错"

软件测试方法大汇总_其它

软件测试方法大汇总 软件测试方法种类繁多,记忆起来混乱, 如果把软件测试方法进行分类, 就会清晰很多. 我参考一些书籍和网上的资料, 把常用的软件测试方法列出来, 让大家对软件测试行业有个总体的看法. 从测试设计方法分类 测试名称 测试内容 Black box黑盒测试 把软件系统当作一个"黑箱",无法了解或使用系统的内部结构及知识.从软件的行为,而不是内部结构出发来设计测试. White box白盒测试 设计者可以看到软件系统的内部结构,并且使用软件的内部知识来指导测试数据及方法的选择

机器学习资料大汇总

机器学习资料大汇总 作者:我爱机器学习(52ml.net) 注:本页面主要针对想快速上手机器学习而又不想深入研究的同学,对于专门的researcher,建议直接啃PRML,ESL,MLAPP以及你相应方向的书(比如Numerical Optimization,Graphic Model等),另外就是Follow牛会牛paper,如果谁有兴趣也可以一起来整理个专业的汇总页.本页面将持续更新,敬请关注,如有推荐的文章请留言,谢谢! 000 开源工具 机器学习的开源工具 Python机器学习库 C++

抢票软件哪个好?2014抢票软件大汇总

新的2014即将来临,一年一度的抢票运动会也将拉开序幕了!铁老大已经在新年前夕上线了新版12306购票页面,并带来官方性质的抢票功能.但相较于我们熟悉的那些抢票插件来说,还是弱了太多.总结往年相关经验,我们提前演练下了解下,这样才能在新的抢票比赛中胜出,安心的踏上回家之路!到底抢票软件哪个好呢?小编今天做了一个汇总,大家可以逐一下载. 最佳推荐 12306.cn 理由:官方出品,安全保证. 首先推荐12306的原因只有一点,那就是他的官方性质,还有......放票前几分钟的修改代码屏蔽抢票插件.

关于博客笔记大汇总,持续更新迭代

目录介绍 1.关于知识图谱 1.1 关于Android知识图谱 1.2 关于博客类型知识图谱 1.3 关于印象笔记思维导图 2.关于开源的项目[13个] 2.1 开源项目思维导图 2.2 开源项目说明 2.3 开源项目迭代更新说明 3.关于技术博客内容[25篇] 3.1 技术博客思维导图 3.2 技术博客说明 3.3 技术博客更新记录日志 4.关于生活博客内容[12篇] 4.1 生活博客思维导图 4.2 生活博客说明 4.3 生活博客更新记录日志 5.关于喜马拉雅音频[139个] 5.1 喜马拉