设计模式——单例模式 Java源代码

最后更新于:2022-04-01 20:01:59

四个不同单例模式写法的Java源代码 - 懒汉模式 - 饿汉模式 - 懒汉模式 + 安全线程 - 双重检验锁 第一个懒汉模式线程不安全,后三个都是线程安全的。四者的共有特点,也是单例模式的最主要特点,是其**构造函数是私有的**。还有一个特点是,有一个**静态**的getInstance() 方法,静态方法就是类方法。 # 懒汉模式 这个写法是GoF提出单例模式时候给出的例子,影响力大,写法简单。 ~~~ package singleton; // 懒汉模式 public class SingletonLazy { private static SingletonLazy uniqueInstance; private SingletonLazy(){} // 私有的构造函数 public static SingletonLazy getInstance() { if(uniqueInstance == null) { uniqueInstance = new SingletonLazy(); } return uniqueInstance; } public String toString() { return "A simple way to apply Singleton, but is not thread safe"; } } ~~~ 然而问题在于:当有多于一个线程的时候,懒汉模式可能失效。举个例子: 1. 线程A和线程B,相隔0.1纳秒分别执行getInstance()方法,A先于B 1. 时刻”T 纳秒”, A发现 uniqueInstance==null,准备开始new SingletonLazy( ) 1. 时刻”T+0.1 纳秒”, **A还没来得及new完SingletonLazy对象**,**此时B发现 uniqueInstance==null**,也准备开始new SingletonLazy( ) 1. 时刻”T+0.5 纳秒”, A成功new完了SingletonLazy对象,uniqueInstance!=null 1. 时刻”T+0.6 纳秒”, B成功new完了SingletonLazy对象 可以看出A,B两个线程都new了SingletonLazy对象,懒汉模式失效。原因在于:A和B两个线程相隔非常非常短的时间分别执行getInstance(),而且new SingletonLazy对象这个过程需要花费一定的时间。 # 饿汉模式 饿汉模式写法,是一上来(类加载的时候)就给你实例一个Singleton对象,不管你此时需不需要。回顾一下懒汉模式写法,你一开始不需要Singleton对象,然后程序运行到某一时刻,第一次调用getInstance()方法,才实例一个Singleton对象。饿汉模式的写法,由JVM保证是安全的(虽然内部机制我不懂,我才刚开始学Java),不过简单想一想,Singleton类加载之前,肯定不会有线程new Singleton(),此时Singleton()的构造函数还不存在呢~ 可以这么说,饿汉模式解决线程安全问题的方法是:从根子上回避这个问题。想法很好,写法很简单,不过呢要多花费一些空间(牺牲空间,换取时间,这个世界就是很难有两全其美的事情) ~~~ package singleton; // 饿汉模式 public class SingletonEager { private static SingletonEager uniqueInstance = new SingletonEager(); // 在这里 new private SingletonEager(){} // 私有的构造函数 public static SingletonEager getInstance() { return uniqueInstance; } public String toString() { return "Create the unique instance when the class is loaded, which is thread safe"; } } ~~~ # 懒汉模式(线程安全) 在懒汉模式的基础上,在getInstance() 方法的声明中,增加关键词synchronized,就可以实现线程安全了。毕竟同步嘛,线程A和B即使时间相隔非常非常短,比如相隔0.1纳秒,那也是分先后呀。就因为A快上0.1纳秒,所以就”捷足先登“了,拿到了锁!B在0.1纳秒后,发现getInstance()方法上了锁,进不去了。 ~~~ package singleton; public class SingletonThreadSafe { private static SingletonThreadSafe uniqueInstance; private SingletonThreadSafe(){} // 私有的构造函数 // 这里同步了 public static synchronized SingletonThreadSafe getInstance() { if(uniqueInstance == null) { uniqueInstance = new SingletonThreadSafe(); } return uniqueInstance; } public String toString() { return "The getInstance() method is declared with keyword 'synchronized'," + " which is thread safe, but with low performance"; } } ~~~ 不过《Head First Design Pattern》说:Just keep in mind that synchronizing a method can decrease performance by a factor of 100。相差100倍,这对于程序性能的影响是相当的大呀! # 双重检验锁 这个是上面的synchronized方法的升级版本。仔细想一想,只有在第一次getInstance()的时候,才需要new singleton对象,对吧?如果不是第一次getInstance(),那就说明singleton对象已经存在了~于是有了下面的优化代码 ~~~ package singleton; // double checked locking public class SingletonDCL { // 注意这个关键词 private volatile static SingletonDCL uniqueInstance; private SingletonDCL(){} // 私有的构造函数 public static SingletonDCL getInstance() { if(uniqueInstance == null) // check once { synchronized(SingletonDCL.class) { if(uniqueInstance == null) // check twice { uniqueInstance = new SingletonDCL(); } } } return uniqueInstance; } public String toString() { return "A thread safe way to apply Singleton with good performance"; } } ~~~ synchronized的不是一个方法,而是一个方法里面的一个代码块,这样被synchronized的部分减少了。 - 注意1:synchronized前后分别check两次。第一个check,Singleton是否曾经被实例化过;第二个check,就相当于上一个例子”懒汉模式+线程安全“中的check - 注意2:volatile关键词,这涉及到JVM内部的机制,先强行记住就行了。 # 测试及补充 ~~~ package singleton; public class Main { public static void main(String[] args) { SingletonLazy singletonLazy = SingletonLazy.getInstance(); SingletonLazy singletonLazy2 = SingletonLazy.getInstance(); System.out.println(singletonLazy); if(singletonLazy2.equals(singletonLazy)) { System.out.println("true"); // 同一个引用 } else { System.out.println("false"); } } } ~~~ 运行结果: ~~~ A simple way to apply Singleton, but is not thread safe true ~~~ **补充:** 《Head First Design Pattern》书中单例模式就这4种写法。不过其实还有更多写法,在实验楼网站中,就还有静态内部类写法和枚举类型写法。《Head First Design Pattern》在GitHub的代码中,有一个例子为了让单例模式能够派生出子类,把构造函数和静态数据成员声明为protected(子类访问权限)。 深入单例模式以及其他设计模式,[猛戳这里(可以在新标签页中打开~)](http://blog.csdn.net/u013390476/article/details/50333763) [某大神博客,C++版本的单例模式,我觉得写的不错](http://blog.csdn.net/fu_zk/article/details/11892095) [这个链接好,C++博大精深。。。](http://segmentfault.com/q/1010000000593968)
';