概念
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
特点
单例类只能有一个实例。
单例类的实例必须由该单例类自行创建。
单例类必须为所有其他对象提供这一实例。
实例
1、懒汉式(非线程安全)
public class Singleton1 {
private Singleton1() {}
private static Singleton1 instance=null;
public static Singleton1 getInstance(){
if (instance==null){
instance=new Singleton1();
}
return instance;
}
}
Singleton1类通过将构造方法修饰符限制为private,防止被其他外部类实例化,它的唯一实例只能通过getInstance()方法获得(通过Java反射机制可以访问类的私有方法并实例化,此处暂不讨论)。该单例模式为懒汉式单例,并没有考虑到并发环境下的线程安全问题,故并发环境下有可能出现多个实例。
2、懒汉式(线程安全)
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
public static synchronized Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
这种实现方式能够在并发环境中线程安全,并且实现了懒加载。但是效率很低,大多数情况下是不需要同步的。
3、 饿汉式
public class Singleton3 {
private Singleton3(){}
private static Singleton3 instance=new Singleton3();
public static Singleton3 getInstance(){
return instance;
}
}
这种基于类加载机制能够保证在并发环境中线程安全,没有实现懒加载。但是,该实例在类加载过程中就实例化,而大多数情况下都是调用getInstance()方法之后才使用该实例,并不需要在类加载时初始化该实例。
4、懒汉式-双重校验锁
public class Singleton4 {
private volatile static Singleton4 instance;
private Singleton4(){}
public static Singleton4 getInstance(){
if (instance==null){
synchronized (Singleton4.class){
if(instance==null){
instance=new Singleton4();
}
}
}
return instance;
}
}
5、懒汉式-静态内部类
public class Singleton5 {
private Singleton5(){}
private static class LazyHolder{
private static final Singleton5 instance=new Singleton5();
}
public static final Singleton5 getInstance(){
return LazyHolder.instance;
}
}
这种方式利用了JVM的类加载机制来保证初始化实例时的线程安全问题,但是它与方法3的区别是:方法3只要在Singleton类被装载时instance就会被实例化。而Singleton5类被装载时,LazyHolder并没有被主动引用,所以不会实例化instance,当调用getInstance()方法时,LazyHolder被主动引用,故需要实例化instance,这种方式既保证了实例懒加载,又保证了线程安全问题,相对比较合理。
6、枚举实现
enum Singleton7 {
INSTANCE;
}
问题
如果单例由不同的类装载器装入,便有可能存在多个实例。例如,某些servlet容器对每个servlet使用完全不同的类装载器,如果有两个servlet访问一个单例类,就会有各自的实例。
如果Singleton类实现了java.io.Serializable接口,该实例可能被序列化和反系列化。则反序列化的多个对象,就会存在多个实例。
解决
其中第二个问题的解决方法在《Effective Java》(第二版)77条,即重写readResolve()方法,具体如下:
private Object readResolve() {
return INSTANCE;
}