创建型--单例模式
概念
保证一个类仅有一个实例,并提供一个访问该实例的全局访问点。
该模式涉及到一个单一的类,该类自己负责创建自己的对象,同时确保只能有单个对象被创建。同时这个类提供了一种访问该唯一对象的方式,可以直接访问,不需要对类进行实例化。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须为所有其他对象提供这一实例的访问入口。
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
构造函数是私有的。
getInstance() 方法中需要使用同步锁 synchronized 防止多线程同时进入造成多次实例化。
单例模式的实现方式
懒:是指延迟初始化(Lazy初始化)。饿,则相反。
线程安全:是指支持多线程下的单例。线程不安全,则相反。
方式一:懒汉式。线程不安全。定义唯一实例 instance 时不赋初值(Lazy)。
package singleton;
public class Singleton {
//getInstance方法是static,则在该方法中能使用的成员只能也是static的
//不能不通过方法访问点直接在类外部访问成员,故设为private
private static Singleton instance;
//private构造器使对象不能在类外创建
private Singleton() { }
//static:private构造器使得该方法不能在类外部用对象调用
public static Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}
}
优点:第一次调用才初始化,避免内存浪费。
缺点:不支持多线程下的单例。
方式二:懒汉式。线程安全。(Lazy)
//和方式一区别在于 getInstance 方法加了锁。
package singleton;
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static synchronized Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}
}
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证多线程下的单例,但加锁会影响效率。
使用环境:getInstance方法使用不太频繁时。
方式三:饿汉式。线程安全。定义唯一实例时会赋初值(非Lazy)
它基于 classloader 机制避免了多线程的同步问题,保证初始化 instance 时只有一个线程在工作。即使未必是因为调用getInstance导致类的初始化,也不能肯定一定是延迟初始化。
package singleton;
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存,易产生垃圾对象。
方式四:双检锁/双重校验锁。线程安全。(DCL,即 double-checked locking)(Lazy)
package singleton;
public class Singleton {
private volatile static Singleton instance; //这里 volatile
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class){ //这里 synchronized ()
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优点:安全,在多线程情况下能保持高性能。
使用环境:getInstance() 的性能对应用程序很关键时。
方式五:登记式/静态内部类。线程安全。(Lazy)
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。
package singleton;
public class Singleton {
private static class SingletonHolder{ //私有静态内部类
private static final Singleton INSTANCE = new Singleton(); // private static final
}
private Singleton() { }
public static final Singleton getInstance() { // final
return SingletonHolder.INSTANCE; //
}
}
方式六:枚举。线程安全。(非Lazy)
package singleton;
public enum Singleton {
INSTANCE;
}
优点:简洁,能避免多线程同步问题,自动支持序列化机制,可防止反序列化重新创建新的对象。