单例模式
介绍: 单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该对象。
意图: 保证一个类仅有一个实例,并提供了一个访问它的全局访问点。
单例模式的几种实现方式
1.饿汉式
描述: 这种方式比较常用,但容易产生垃圾对象。
优点: 没有加锁,执行效率会提高。
缺点: 类加载时就初始化,浪费内存。
它基于classloader机制避免了多线程的同步问题,不过,instance在类加载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法,但是也不能确定有其他的方式(或者其他的静态方法)导致类加载,这时候初始化instance显然没有达到(lazy loading)的效果。
代码示例:
package Singleton;
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
2.懒汉式(线程不安全)
描述: 这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁synchronized,所以严格意意义上它并不算单例模式。
优点: lazy loading很明显。
缺点: 线程不安全。
代码示例:
package Singleton;
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
3.懒汉式(线程安全)
描述: 这种方式具备很好的lazy loading,能够在多线程中很好的工作,但是,效率很低,99%的情况下不需要同步。
优点: 第一次调用才初始化,避免内存浪费。
缺点: 必须加锁synchronized才能保证单例,但加锁会影响效率。
代码示例:
package Singleton;
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
4.懒汉式(双重锁/双重校验锁(DCL,即double-checked locking))
描述: 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
代码示例:
package Singleton;
public class Singleton {
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
5.登记式/静态内部类
描述: 这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了classloader机制来保证初始化instance时只有一个线程,它跟饿汉式不同的是:饿汉式只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,所以想让它延迟加载,另外一方面,又不希望在Singleton类加载时就实例化,因为不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然不合适。这个时候,这种方式相比饿汉式就显得很合理。
代码示例:
package Singleton;
public class Singleton {
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.instance;
}
}
6.枚举
JDK版本:JDK1.5起。
描述: 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是Effective Java作者Josh Bloch提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于JDK1.5之后才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
代码示例:
package Singleton;
public enum Singleton {
INSTANCE;
private Singleton(){}
public void whateverMethod(){
}
}
使用方式:
Singleton.INSTANCE.whateverMethod()
总结:一般情况下,不建议使用第一种和第二种懒汉模式,建议使用饿汉模式。只有在要明确时限lazy loading效果时,才会使用第五种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第六种枚举方式。如果有其他特殊的需求,可以考虑使用双检锁方式。