定义
- Ensure a class has only one instance,and provide a global point of access to it(确保某一个类只有一个实例,而且自行实例化并向这个系统提供这个实例)
优点
- 内存中只有一个实例,减少了内存开支。特别是当一个对象需要频繁的创建销毁时
- 只生成一个实例,减少系统性能开销。(当一个对象产生需要比较多的资源如读取配置文件的时候,可以在应用启动时产生一个单例对象,然后用永久驻留内存的方式解决(注意垃圾回收机制))
缺点
- 单例模式一般没有接口,扩展很难(接口对单例模式没有任何意义)
- 单例模式对测试不利(并行开发环境下,如果单例模式没有完成,是不能够测试的)
使用场景
- 需求:要求一个类有且仅有一个对象,如果多个对象就会出现不良反应
- 举例
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据
- 创建一个对象需要消耗的资源过多。如访问IO和数据库等资源
- 需要定义大量的静态常量和静态方法,如工具类(也可以直接使用static)
分类
通用类图
懒汉式单例
在使用时创建单例对象。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.pattern.singleton;
public class SingletonLazy { private SingletonLazy() { };
private static SingletonLazy singletonLazy = null;
public static SingletonLazy newInstance() { if (singletonLazy == null) { singletonLazy = new SingletonLazy(); } return singletonLazy; }
}
|
存在问题
- 线程不安全(高并发的情况下),如:线程A执行到new的时候,但是还没有获取到对象,线程B执行到 对象==null判断,此时B判断为true于是就会创建一个对象,这时A也创建对象就会导致有两个实例。
懒汉式单利Synchronized
使用synchronized解决单例模式线程不安全的问题,但是存在效率问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.pattern.singleton;
public class SingletonLazySynchronized { private static SingletonLazySynchronized instance; private SingletonLazySynchronized(){} public synchronized static SingletonLazySynchronized getInstance(){ if(instance != null){ instance = new SingletonLazySynchronized(); } return instance; } }
|
懒汉式单例DCL
双重锁定检查的单例模式,注:此模式也不一定线程安全,因为有指令重排的存在
某一线程执行到第一次检测时,读取到instance!=null,instance的引用对象可能没有完成初始化
instance = new SingletonLazyDCL();可以分为3步
- 分配内存空间:memory = allocate();
- 初始化对象:instance(memory)
- 设置instance指向刚分配的内存地址,此时instance != null:instance = memory
其中步骤2和3**不存在数据依赖关系**,而且无论重排前还是重排后在执行结果在单线程中没有改变。所以这种重排优化是允许的(2步3步交换顺序)
指令重排后,当一条线程访问instance不为null时,由于instance未必已经初始化完成,也就造成了线程安全的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.pattern.singleton;
public class SingletonLazyDCL { private static SingletonLazyDCL instance; private SingletonLazyDCL(){} public static SingletonLazyDCL getInstance(){ if(instance == null){ synchronized (SingletonLazyDCL.class){ if(instance == null){ instance = new SingletonLazyDCL(); } } } return instance; } }
|
饿汉式单例DLC+Volatile
解决由于指令重排导致的线程不安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.pattern.singleton;
public class SingletonLazyDCLVolatile { private static volatile SingletonLazyDCLVolatile instance; private SingletonLazyDCLVolatile(){} public static SingletonLazyDCLVolatile getInstance(){ if(instance == null){ synchronized (SingletonLazyDCLVolatile.class){ if(instance == null){ instance = new SingletonLazyDCLVolatile(); } } } return instance; } }
|
静态内部类单例
通过静态内部类的方式实现单例模式是线程安全的,同时静态内部类不会在Singleton类加载时就加载,而是在调用getInstance()方法时才进行加载,达到了懒加载的效果。
1 2 3 4 5 6 7 8 9 10 11 12
| package com.pattern.singleton;
public class SingletonInnerClass { private static class InnnerClass{ private static SingletonInnerClass instance = new SingletonInnerClass(); } private SingletonInnerClass(){} public static SingletonInnerClass getInstance(){ return InnnerClass.instance; } }
|
枚举单例
算是最好的一种单例模式了吧,可以防止反序列化以及反射带来的问题。
1 2 3 4 5 6 7 8 9
| package com.pattern.singleton;
public enum SingletonEnum { INSTANCE; public void singletonMethod(){ System.out.println("singletonMethod"); } }
|
饿汉式单例
在初始化时候直接创建对象。
代码
1 2 3 4 5 6 7 8 9 10 11
| package com.pattern.singleton;
public class SingletonEager { private SingletonEager(){}; private static SingletonEager singletonEager = new SingletonEager(); public static SingletonEager newInstance(){ return singletonEager; } }
|
有上限的多例
有上限的多例是单例模式的一个变种,通过容器来限制多例的数量。
如:写日志文件,分别写在两个不同的路径。此时就可以使用有上限的多例来为每个路径的日志文件分配一个对象。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.pattern.singleton;
import java.util.ArrayList;
public class LimitMultiton { private static int MAX_INSTANCE_COUNT = 2; private static ArrayList<LimitMultiton> instanceList = new ArrayList<>(MAX_INSTANCE_COUNT); private LimitMultiton(){}; static{ for (int i = 0; i < MAX_INSTANCE_COUNT; i++) { instanceList.add(new LimitMultiton()); } } public static int getInstanceCount(){ return MAX_INSTANCE_COUNT; } public static LimitMultiton newInstance(int index){ return instanceList.get(index); } }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.pattern;
import java.util.Random;
import com.pattern.singleton.LimitMultiton; import com.pattern.singleton.SingletonEager; import com.pattern.singleton.SingletonLazy;
import org.junit.Test;
public class SingletonTest { @Test public void SingletonEagerTest(){ SingletonEager singletonEager = null; for (int i = 0; i < 10; i++) { singletonEager = SingletonEager.newInstance(); System.out.println(singletonEager); } } @Test public void SingletonLazyTest(){ SingletonLazy singletonLazy = null; for (int i = 0; i < 10; i++) { singletonLazy = SingletonLazy.newInstance(); System.out.println(singletonLazy); } } @Test public void LimitMultitonTest(){ LimitMultiton limitMultiton = null; for (int i = 0; i < 20; i++) { limitMultiton = LimitMultiton.newInstance(new Random().nextInt(LimitMultiton.getInstanceCount())); System.out.println(limitMultiton); } } }
|
注意
- 单例类不要实现Cloneable,否则可以通过对象复制来创建一个新的单例对象(Java中,如果类实现了Cloneable接口,并实现了clone方法,可以直接通过对象复制方式创建一个新的对象)