1 介绍:
单例模式是很是简单、很是基础的一种建立型设计模式。单例模式是在Java开发(不管是android开发,仍是Java服务器端开发)中常用的一个设计模式。因为在Java服务器端开发中常用,因此单例模式的实现还涉及到了Java并发编程。在java面试中,经常被要求使用java实现单例模式,所以有必要熟练掌握。本文是本身学习java单例模式的一个总结,参考了网上的不少资料,大部份内容不是原创。在这里要向参考文献的做者表示感谢。
本文组织结构以下: 2 介绍单例模式的java实现到底有多少种;3-9分别介绍这些实现方式; 10 提出一些进一步的问题; 11 总结本文。html
2 到底有多少种实现?
"【深刻】java 单例模式"一文列出了5种实现方式,一种为GOF的、线程不安全的实现方式,剩下4种是线程安全(thread-safe)的实现方式,分别为:同步方法(synchronized function)方式、双重检查加锁(DCL,double-checked locking)方式、急切加载方式 和内部类方式。
"Java:单例模式的七种写法"一文列出了7种实现方式:线程不安全方式、同步方法方式、急切加载方式(及其变种)、静态内部类方式、枚举方式和双重校验锁方式。 但急切加载方式及其变种应该统一看作是一种实现。
因此本文的结论就是: java单例模式一共有下面将要介绍的5种实现方式。
java
4. 第一种: 线程不安全方式(GOF方式\经典方式) ( 懒加载)
android
这是GOF和HeadFirstDesignPattern中介绍的最经典的一种方式。这种方式在单线程状况下能够实现懒加载,但在多线程状况下会出问题。面试
1 public class Creation_singleton { 2 // private static instance, which is default null 3 private static Creation_singleton instance; 4 // private constructor 5 private Creation_singleton () { 6 } 7 // public get instance function 8 public static Creation_singleton getInstance ( ) { 9 if ( instance == null ) { 10 instance = new Creation_singleton(); 11 } 12 return instance; 13 } 14 }
5. 第二种:简单加锁方式 ( synchronized )
编程
由第一种而来,很天然会想到,保证线程安全的方式能够是使用synchronized关键字。这种方式是线程安全,又是懒加载。但使用synchronized 会下降性能。
设计模式
1 public class Creation_singleton { 2 private static Creation_singleton instance; 3 private Creation_singleton () { } 4 public static Synchronized Creation_singleton getInstance ( ) { 5 if ( instance == null ) { 6 instance = new Creation_singleton(); 7 } 8 return instance; 9 } 10 }
咱们必须避免使用synchronization,由于它很慢。----上述论断在必定程度上已经不是事实了,由于在现代JVM上,若是是无竞争的同步的话,synchronized并不会很慢了。可是当在多线程环境中,多个线程同时竞争getInstance方法时,仍是可能会致使性能降低。安全
之因此说上述论断不是事实,是与显式锁进行比较得出的结论。内置锁一度被认为比Lock显式锁的性能低不少,但随着JVM的优化,synchronized关键字的性能已经不比Lock低不少了。服务器
6. 急切加载方式 ( 变种 )
多线程
1 class Singleton { 2 //私有,静态的类自身实例 3 private static Singleton instance = new Singleton(); 4 //私有的构造子(构造器,构造函数,构造方法) 5 private Singleton(){} 6 //公开,静态的工厂方法 7 public static Singleton getInstance() { 8 return instance; 9 } 10 }
这种方式有一种变种,就是使用静态初始化块来初始化单例。
有人说这种方式有个缺点:其实跟全局变量是同样的: 无论该实例是否是到了实际被使用的时刻,也无论应用程序最终是否是使用该单例,在类加载 时都会建立该单例,因此叫“急切建立”, 可是这样一来,单例模式的延迟建立的优势就没有了。 因此该版本与全局变量的方式并无区别(?yes) static 成员变量只有在类加载的时候初始化一次。类加载是线程安全的。 因此该方法实现的单例是线程安全的。 (static的全局变量也是线程安全的)
可是jvm的类加载也是懒加载的,并非一开始启动jvm的时候就所有加载全部类。加载这个类,跟建立这个类的实例,两者的时机,在大部分时候,应该是同时的,也就是说,应该不存在不少跟建立这个类的实例无关的、须要加载这个类的状况。正是因为这个缘由,这种方式,在NTS代码中使用的不少。
7. 双重检查加锁(dcl)方式
在生产环境中能够看到以下代码,是双重检查加锁的实现。双重检查加锁是简单加锁方式的一种“自做聪明”的改进,其实这种方式在多线程环境下会失败。
并发
1 private static SocketFactory instance = null; 2 3 private static SocketFactory getInstance() 4 { 5 if (instance == null) 6 { 7 synchronized (SocketFactory.class) 8 { 9 if (instance == null) 10 { 11 instance = new SocketFactory(); 12 } 13 } 14 } 15 return instance; 16 }
双重检查加锁方式也能够正确地被实现,前提是对上述双重检查加锁方式作出必定的改进。这也让双重检查加锁方式成为了最复杂的实现方式。所以这种方式是不被推荐的。 双重检查加锁方式的正确实现由三种:volatile方式、 final方式、 temp instance方式。由于temp instance 方式很是的繁琐,在本文中就再也不作介绍了,有兴趣的读者能够去参考文献中找答案。
在Java5中,DCL其实是可行的,若是你给instance域加上volatile修饰符。例如,若是咱们须要给getInstance()方法传递一个database connection的话,那么如下代码是可行的:
1 public class MyFactory { 2 private static volatile MyFactory instance; 3 4 public static MyFactory getInstance(Connection conn) 5 throws IOException { 6 if (instance == null) { 7 synchronized (MyFactory.class) { 8 if (instance == null) 9 instance = new MyFactory(conn); 10 } 11 } 12 return instance; 13 } 14 15 private MyFactory(Connection conn) throws IOException { 16 // init factory using the database connection passed in 17 } 18 }
可是须要注意的是,上述代码仅仅在Java5及其以上版本中才能够,由于Java5对volatiel关键字的语义进行了修正。(Java4及其之前版本中Volatile关键字的语义都不是彻底正确的)。当使用Java5获取一个volatile变量时,获取行为具备synchronization同步语义。换句话说,Java5保证了以下的Happen-before规则:对volatile变量的未同步的读操做必须在写操做以后才能发生,从而读线程将会看到MyFactory对象的全部域的正确值。
7.2 把instance域声明为final
从java 5 以后才有的新特性之一是,final 域的语义有了新的变化。这些域的值是在构造函数中被赋值的,JVM会确保这些值在这个对象引用本身以前被提交到主内存。
换句话说,若是其余线程能够看到这个对象,那么它们永远不可能看到这个对象的final域的未经初始化的值。因此在这种状况下,咱们将不须要把这个instance的引用声明为volatile。
(问题:是否须要把全部域都声明为final? )
8. 静态嵌套类方式
1 public class Creation_singleton_innerClass { 2 private static class SingletonHolder{ 3 private static Creation_singleton_innerClass instance = new Creation_singleton_innerClass(); 4 } 5 private Creation_singleton_innerClass() { } 6 public static Creation_singleton_innerClass getInstance() { 7 return SingletonHolder.instance; 8 } 9 }
该方式与急切建立方式有些相似。 java机制规定,内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy), 说明内部类的加载时机跟外部类的加载时机不一样。 并且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次instance,之后不会再初始化。
该实现方式能够有一些小变化:instance能够加一个final修饰;静态嵌套类能够改成内部类。
9. 枚举方式
1 enum Singleton_enum { 2 INSTANCE; 3 void method () { 4 } 5 }
该方式是《effective java》中推荐的方法。 枚举类型是在java1.5中引入的,enum能够看作是一种特殊的class,除了不能继承。INSTANCE是Singleton_enum类的实例。
上述代码会被JVM编译为:
1 final class MySingleton { 2 final static MySingleton INSTANCE = new MySingleton(); 3 void method () { } 4 }
虽然enum的域是编译时常量,但他们是对应enum类型的实例,他们仅在对应enum类型首次被引用的时候被生成。当代码首次运行到访问INSTANCE处时,类MyStingleton才会被JVM装载和初始化。该装载和初始化过程只初始化static域一次。
10 进一步问题:
我在一次面试中,被问到以下问题:单例模式与static方法的区别是什么? 当时没回答上来,如今把答案整理以下:
10.1 单例模式与 static方法的简单比较
static 方法没法实现单例;
若是类与其余类的交互比较复杂,容易形成一些跟初始化有关的、很难调试的bug;
static是急切初始化。
static方法最大的不一样就是不能做为参数传递给别的方法。单例能够把这个单例object做为参数传递给其余方法,并当作普通对象来使用。一个静态类只容许静态方法。11 参考文献:http://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.htmlhttp://www.blogjava.net/kenzhh/archive/2015/04/06/357824.htmlhttp://www.raychase.net/257https://en.wikipedia.org/wiki/Singleton_pattern