最近学习了一下单例模式,整理如下,如有错误和不足之处欢迎各位批评纠正,互相学习!
第一种:懒汉式,线程不安全
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 懒汉,线程不安全, 在单进程的时候工作正常,但在多线程的情况下就不能正常工作。
*/
public class Singleton1 {
private static Singleton1 instance;
private Singleton1 () {}
public static Singleton1 getInstance () {
if (null == instance) {
instance = new Singleton1();
}
return instance;
}
}
懒汉式,线程不安全, 在单进程的时候工作正常,但在多线程的情况下就不能正常工作。如果两个线程同时运行到判断instance是否为null的if语句,并且instance的确没有创建时,那么两个线程都会创建一个实例,此时类型Singleton1就不再满足单例模式的要求了。习!
package singleton;
/**
* Created by MJ on 15/10/7.
* @use 懒汉式,线程安全
*/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
public static synchronized Singleton2 getInstance() {
if (null == instance) {
instance = new Singleton2();
}
return instance;
}
}
懒汉式,线程安全。如果两个线程同时想创建一个实例。由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁时,第二个线程只能等待。当第一个线程发现实例还没有创建时,它创建出一个实例。接着第一个线程释放同步锁,此时第二个线程可以加上同步锁,并运行接下来的代码。这个时候由于实例已经被第一个线程创建出来了,第二个线程就不会重复创建实例了,这样就保证了在多线程环境中也只能得到一个实例。
第三种:饿汉式
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 饿汉式,类加载时就会实例化
*/
public class Singleton3 {
private static Singleton3 instance = new Singleton3();
private Singleton3() {}
public static Singleton3 getInstance() {
return instance;
}
}
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候会过早地实现单例模式,从而降低内存的使用效率。
第四种:饿汉,变种
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 饿汉式变种,在类初始化时实例化instance
*/
public class Singleton4 {
private static Singleton4 instance = null;
static {
instance = new Singleton4();
}
private Singleton4() {}
public static Singleton4 getInstance() {
return instance;
}
}
表面上看起来差别挺大,其实更第三种方式差不多,是在类初始化即实例化instance
第五种:静态内部类
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 静态嵌套类式,利用了classloder的机制来保证初始化instance时只有一个线程,而类被装载时,instance不一定被初始化。
*/
public class Singleton5 {
private static class SingletonNested {
private static final Singleton5 INSTANCE = new Singleton5();
}
private Singleton5() {}
public static final Singleton5 getInstance() {
return SingletonNested.INSTANCE;
}
}
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
第六种:双重校验锁
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 双重校验式
*/
public class Singleton6 {
private volatile static Singleton6 instance;
private Singleton6() {}
public static Singleton6 getInstance() {
if (null == instance) {
synchronized (Singleton6.class) {
if (null == instance) {
instance = new Singleton6();
}
}
}
return instance;
}
}
这个是第二种方式的升级版,俗称双重检查锁定,详细介绍请查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html
在JDK1.5之后,双重检查锁定才能够正常达到单例效果。
第七种:枚举
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 枚举式
*/
public enum Singleton7 {
INSTANCE;
public void whateverMethod(){}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
对我个人来说,我比较喜欢第三种和第五种方式,简单易懂,而且在JVM层实现了线程安全(如果不是多个类加载器环境),一般的情况下,使用第三种方式,只有在要明确实现lazy loading效果时才会使用第五种方式,另外,如果涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。