对于系统中的某些类来说,只有一个实例很重要,如一个系统中只有一个计时工具和 ID(序号)生成器。
单例模式适用情况包括:系统只需要一个实例对象;客户调用类的单个实例只允许使用一个公共访问点。
顾名思义,用来保证一个对象只能创建一个实例,除此之外,它还提供了对实例的全局访问方法。
单例模式的要点有三个:一是类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
为了确保单例实例的唯一性,所有的 单例构造器都要被声明为私有 的,再通过声明 静态方法实现全局访问获得 该单例实例。
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null)
instance = new Singleton();
return instance;
}
public void doAction(){
//TODO 实现你需要做的事
}
}
懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是 synchronized
关键字。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
public void doAction(){
//TODO 实现你需要做的事
}
}
饿汉式,从名字上也很好理解,就是“比较勤”,实例在 初始化 的时候就已经建好了,不管你有没有用到,都先建好了再说。好处是 没有线程安全的问题 (利用类加载机制避免了多线程同步问题),坏处是浪费内存空间。
public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null)
synchronized (Singleton.class){
if(instance == null)
instance = new Singleton();
}
return instance;
}
public void doAction(){
//TODO 实现你需要做的事
}
}
优点:
1、第一个 if 校验是为了 提高代码执行效率,由于单例模式只需要创建一次实例即可,所以当实例创建后,再次调用 getInstance 方法就不用再竞争所进入同步代码块,直接返回前面创建好的实例即可。
2、第二个 if 校验是为了 防止二次创建实例。由于第一次 if 校验没有同步,有可能多个线程都进入到了第一个 if 里面竞争资源,假如没有第二次校验, t1,t2 都在竞争同步资源,t2 获取到资源后,创建实例,然后资源释放,t1 获取到资源, t1 就也会创建一个实例,那么,就会出现创建多个实例的情况,所以,第二次 if 校验可以 完全避免多线程导致创建多次实例的问题。
private volatile static Singleton instance = null; 这里的
volatile
必不可少,volatile
关键字可以避免 JVM 指令重排优化。因为 instance = new Singleton(); 可以拆分为三步:
- 为 singleton 分配内存空间;
- 初始化 singleton;
- 将 singleton 指向分配的内存空间;
但由于 JVM 具有指令重排序的特性,执行顺序可能变为 1-3-2。指令重排序在单线程下不会出现问题,但是在 多线程下会导致线程获取一个未初始化的实例。例如:线程 T1 执行了 1 和 3,此时 T2 调用 getInstance() 后发现 singleton 不为空,因此返回 singleton,但是此时的 singleton 还没有被初始化。
使用volatile
会禁止 JVM 指令重排,从而保证在多线程下也能正常执行。
public class Singleton {
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
public void doAction(){
//TODO 实现你需要做的事
}
}
这种方式利用了类装载机制来保证初始化实例时只有一个线程,静态内部类在 Singleton 被装载时并不会立即实例化,而是在调用 getInstance() 时才会装载静态内部类,从而完成 Singleton 实例化。由于类的静态属性只会在第一次加载类的时候进行初始化,就通过 JVM 加载类时的线程安全的特性来保证了线程安全。可能还存在反射攻击或者反序列化攻击。
优点:
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
}
自动支持序列化机制,绝对防止多次实例化。
优点:
单例模式的实现方式及如何有效防止防止反射和反序列化 - 叫我鹏爷 - 博客园 (cnblogs.com)
以 Double Check 为例子,测试反射,序列化,克隆是否能破环单例模式:
public class Singleton implements Serializable,Cloneable {
private static volatile Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试用例:
public static void main(String[] args) throws Exception {
Singleton instance = Singleton.getInstance();
System.out.println("原本的 singleton 的 hashcode: " + instance.hashCode());
//反射
Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Singleton singleton = declaredConstructor.newInstance();
System.out.println("反射获取的 singleton 的 hashcode: " + singleton.hashCode());
//克隆
Singleton clone = (Singleton) Singleton.getInstance().clone();
System.out.println("克隆获取的 singleton 的 hashcode: " + clone.hashCode());
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Singleton.getInstance());
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton serialize = (Singleton) ois.readObject();
//关闭资源略
System.out.println("序列化获取的 singleton 的 hashCode: "+ serialize.hashCode());
}
输出结果:
原本的 singleton 的 hashcode: 460141958
反射获取的 singleton 的 hashcode: 1163157884
克隆获取的 singleton 的 hashcode: 1956725890
序列化获取的 singleton 的 hashCode: 666641942
运行结果表明通过 getInstance()、反射、克隆、序列化这四种方式得到的 Singleton 对象的 hashCode 是不一样的,此时单例模式已然被破环
1、防止反射破环(虽然构造方法已私有化,但通过反射机制使用 newInstance() 方法构造方法也是可以被调用):
isFristCreate
默认为开启状态2、防止克隆破环
clone()
,直接返回单例对象3、防止序列化破环
readResolve()
,返回 Object
对象public class Singleton implements Serializable, Cloneable {
private static final long serialVersionUID = 6125990676610180062L;
private volatile static Singleton singleton;
private static boolean isFristCreate = true;//默认是第一次创建
private Singleton() {
if (isFristCreate) {
synchronized (Singleton.class) {
if (isFristCreate) {
isFristCreate = false;
}
}
} else {
throw new RuntimeException("已然被实例化一次,不能再实例化");
}
}
public void doAction() {
//TODO 实现你需要做的事
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
@Override
protected Singleton clone() throws CloneNotSupportedException {
return singleton;
}
private Object readResolve() {
return singleton;
}
}
测试用例:
public static void main(String[] args) throws Exception {
Singleton instance = Singleton.getInstance();
System.out.println("原本的 singleton 的 hashcode: " + instance.hashCode());
//克隆
Singleton clone = (Singleton) Singleton.getInstance().clone();
System.out.println("克隆获取的 singleton 的 hashcode: " + clone.hashCode());
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Singleton.getInstance());
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton serialize = (Singleton) ois.readObject();
//关闭资源略
System.out.println("序列化获取的 singleton 的 hashCode: "+ serialize.hashCode());
//反射
Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Singleton singleton = declaredConstructor.newInstance();
System.out.println("反射获取的 singleton 的 hashcode: " + singleton.hashCode());
}
测试结果:
原本的 singleton 的 hashcode: 460141958
克隆获取的 singleton 的 hashcode: 460141958
序列化获取的 singleton 的 hashCode: 460141958
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at pc.TestSingleton.main(TestSingleton.java:37)
Caused by: java.lang.RuntimeException: 已然被实例化一次,不能再实例化
at pc.Singleton.<init>(Singleton.java:24)
... 5 more
[1] 单例模式的五种写法_absolute_chen的博客-CSDN博客_单例模式
[2] 设计模式(二)单例模式的七种写法_Android进阶三部曲 - 刘望舒-CSDN博客_单例模式写法
[3] 单例模式的实现方式及如何有效防止防止反射和反序列化 - 叫我鹏爷 - 博客园 (cnblogs.com) (推荐)