1.单例设计模式
单例模式是指在系统中,使某些类只有一个实例存在,每次获取该实例对象时,总是获取到同一个对象Instance1==Instance2
。
现在操作系统的任务管理器就是以单例模式来实现的,重复打开也只是唤出同一个窗口。//我怎么记得以前XP还是Win7好像可以打开很多个
像各种连接池,也只需要存在一个实例即可。还有各类引擎、驱动等。
这个设计模式在实际中还是比较常见的一种。
单例模式有三个特点
- 单例实例只能通过单例类进行创建。
- 系统中至多只能存在一个该类的实例。
- 单例类需要对外提供一个方法来获取单例对象。
如果要使实例只能通过该类来进行创建,我们首先应该私有其构造函数,这样该类实例就无法在外部被直接创建出来。
然后我们再提供一个静态方法来供外部获取实例,并且每次获取的实例都是同一个。
单例模式的实现一般有以下几种。
1. 饿汉式单例
之所以叫饿汉式就是因为它很饿,一来就想要吃东西(在类被加载时就创建实例)。
/**
* 饿汉式单例,在该类被加载时就创建了类的实例.
* 唯一的缺点是不能懒加载,占用了资源.
*
* @author : hafuhafu
* @date : 2019/9/11 15:17
*/
public class HungerSingleton {
private static final HungerSingleton instance = new HungerSingleton();
private HungerSingleton() {
}
public static HungerSingleton getInstance() {
return instance;
}
}
因为实例在类被加载的时候就开始创建,所以这种方式是线程安全的,并且实现简单,但是不能Lazy Load,相对来说会占用资源。
2. 懒汉式单例
2.1普通懒汉式
顾名思义,它很懒(不会在一开始就创建实例,和饿汉相反),只有当他第一次用到的时候(调用getInstance方法)的时候,才会创建实例。
/**
* 懒汉式单例.优点是Lazy load,当需要调用的时候才会生成实例.但是该方式是线程不安全的.
*
* @author : hafuhafu
* @date : 2019/9/11 15:05
*/
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
懒汉式单例虽然在需要的时候才创建实例,相对而言节省了空间,但是在多线程情况下的有安全问题。
2.2线程安全的懒汉式
利用关键字 synchronized
使获取实例的方法同步,但是同步加锁会影响性能。
/**
* 线程安全的懒汉式,利用 `synchronized`关键字使方法同步.但是加锁会影响性能.
*
* @author : hafuhafu
* @date : 2019/9/11 15:05
*/
public class SafeLazySingleton {
private static SafeLazySingleton instance;
private SafeLazySingleton() {
}
synchronized public static SafeLazySingleton getInstance() {
if (instance == null) {
instance = new SafeLazySingleton();
}
return instance;
}
}
2.3双检锁单例
/**
* 双检锁单例,线程安全,性能较好.但并不推荐使用该方法
* 虽然理论上完美,但是由于 `instance = new DoubleLockSingleton();` 并非原子操作.
* 在JVM中,这句语句做了以下三件事:
* 1.为 instance 分配内存空间
* 2.调用DoubleLockSingleton的构造函数初始化成员变量
* 3.将instance对象执行分配的内存空间(当这里执行完后,instance就不再为null)
* 但是由于有些编译器中存在指令重排序的优化,导致 步骤2和3顺序不确定.
* 这时我们将变量instance申明为 volatile,可以保证顺序.
*
* @author : hafuhafu
* @date : 2019/9/12 10:19
*/
public class DoubleLockSingleton {
private volatile static DoubleLockSingleton instance;
private DoubleLockSingleton() {
}
public static DoubleLockSingleton getInstance() {
if (instance == null) {
synchronized (DoubleLockSingleton.class) {
if (instance == null) {
instance = new DoubleLockSingleton();
}
}
}
return instance;
}
}
但是我试着在多线程情况下测试了一下,无论有没有volatile关键字似乎都没啥差别,可能刚好我的编译器不会指令重排序吧。
3. 静态内部类单例
/**
* 静态内部类实现单例,其实也是懒汉式,但是是利用JVM机制保证了单例以及线程安全.
* 只有在 `StaticSingleton`的getInstance方法被调用的时候,SingletonHolder才会被加载,其中的INSTANCE才会被初始化.
*
* @author : hafuhafu
* @date : 2019/9/12 10:39
*/
public class StaticSingleton {
private static class SingletonHolder {
private static final StaticSingleton INSTANCE = new StaticSingleton();
}
private StaticSingleton() {
}
public static final StaticSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4.枚举实现单例
枚举是JDK1.5起提供的一个基本数据类型.本质上就是一个普通的类,但是其继承于java.lang.Enum
public enum TaskManagerEnum{
INSTANCE;
}
编译后的字节码被反编译后得到如下代码;
public final class TaskManagerEnum extends Enum<TaskManagerEnum>{
public static final TaskManagerEnum INSTANCE;
public static TaskManagerEnum[] values();
public static TaskManagerEnum valueOf(String s);
static {};
}
每一个枚举类型以及其定义的枚举变量在JVM中都是唯一的。在枚举类型的序列化和反序列化上,Java做了特殊的规定。
在序列化时,Java仅仅将枚举对象的name属性输出到结果中,而反序列化时,则通过java.lang.Enum
的valueOf()
方法来根据名字查找枚举对象。
所以在序列化时,只将名称INSTANCE
输出,反序列化时又通过这个名称查找,前后两个实例对象相同。
因此枚举天生保证序列化单例。
/**
* 枚举由于机制原因,本身以及成员变量都是单例类型
* 枚举是实现单例模式的最佳方式,功能完整,使用简洁,在序列化以及反射中都能够防止多次实例
* 在<<Effective Java>>中被认为是最佳的实现方式
*
* @author : hafuhafu
* @date : 2019/9/12 14:02
*/
public enum FileManager {
INSTANCE;
private String fileName;
private long fileId;
public static FileManager getInstance() {
return INSTANCE;
}
}
/**
* 枚举在内部时.
* 任务管理器是单例设计模式
*
* @author : hafuhafu
* @date : 2019/9/12 13:58
*/
public class TaskManager {
private String taskName;
private long taskId;
private List<Object> subList;
enum TaskManagerEnum {
INSTANCE;
private TaskManager taskManager = new TaskManager();
}
private TaskManager() {
}
public static TaskManager getInstance() {
return TaskManagerEnum.INSTANCE.taskManager;
}
}