1. 定义
在一个应用内某个类只会产生一个实例. 基本实现方式是,私有化构造方法,提供一个获取实例的方法;
2. 解决的问题
如果整个系统中针对某个类只需实例化一次,则可以考虑用单例,解决应用类实例爆炸,也可简化类的调用方式.
3. 例子
单例模式有很多种实现方式:
A. 懒汉式
顾名思义,比较懈怠,就是需要的时候才会去创建实例,这是经典的延迟加载思想,包括缓存的实现思路;它会有线程安全问题,可以加锁,但是会影响效率,典型的时间换空间的策略;
B. 饿汉式
饿汉式就是比较着急,所以会在类加载的时候就创建实例,由于它是在类加载的时候就创建了,所以没有线程安全的问题,但是浪费资源,空间换时间;
C. 静态内部类
结合懒汉饿汉两种实现都会有点小小的缺陷,能不能解决既能延迟加载,又不会有安全问题呢?因为static是由jvm去控制线同步的,所以能解决线程安全问题;在单例类内添加一个静态内部类,静态内部类初始化类实例;
D. 枚举
在高效Java第二版上提到的一种实现方式,因为枚举的一个属性就是代表枚举类自己,所以用来实现单例模式非常简便和通俗易懂;
单例应用实例:在花式刹停项目比赛中,一般会有3~5个裁判,来根据选手使用的动作进行评分(动作分,难度分,表演分),动作有三大类,A级,B级,C级(还有个D级一般不会在比赛中出现),每个等级下面又分很多难度类,所以分数都有高有底。接下来就用最后一个枚举的实现方式来实现一个根据动作进行评分的一个单例类,具体看下面枚举示例代码。
4. 示例代码
package com.bufoon.test.gof.singleton;
/**
* 顾名思义,比较懈怠,就是需要的时候才会去创建实例,
* 这是经典的延迟加载思想,还有缓存的实现思路;
* 它会有线程安全问题,可以加锁,但是会影响效率,
* 典型的时间换空间的策略;
* @ClassName: LazySingleton
* @Description: 懒汉式单例
* @author anling.song
* @date 2017年7月6日 下午4:20:09
*/
public class LazySingleton {
private static LazySingleton instance = null;
//私有化构造方法
private LazySingleton(){}
/**
* 线程安全问题
* @return
*/
public static LazySingleton getInstance1(){
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
/**
* 加锁能避免线程安全问题,但是效率有问题
* 因为每次调用都得先获取锁
* @return
*/
public synchronized static LazySingleton getInstance2(){
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
/**
* 加锁的另外一种实现方式,只会在第一次生成实例的时候去获取锁
* 提高效率,双重判断加锁,如果通过反射还是会破坏单例模式
* @return
*/
public static LazySingleton getInstance3(){
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
package com.bufoon.test.gof.singleton;
/**
* 饿汉式就是比较着急,所以会在类加载的时候就创建实例,
* 由于它是在类加载的时候就创建了,所以没有线程安全的问题,
* 但是浪费资源,空间换时间;
* @ClassName: HungrySingleton
* @Description: 饿汉式单例
* @author anling.song
* @date 2017年7月6日 下午4:17:37
*/
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
//私有化构造方法
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
package com.bufoon.test.gof.singleton;
/**
* 结合懒汉饿汉两种实现都会有点小小的缺陷,
* 能不能解决既能延迟加载,又不会有安全问题呢?
* 因为static是由jvm去控制线同步的,所以能解决线程安全问题;
* 在单例类内添加一个静态内部类,静态内部类初始化类实例;
* @ClassName: InnerStaticClassSingleton
* @Description: 静态内部类单例
* @author anling.song
* @date 2017年7月6日 下午4:29:14
*/
public class InnerStaticClassSingleton {
private InnerStaticClassSingleton(){}
/**
* 在真正调用的时候才会初始化,实现了延迟加载
* 线程安全由jvm底层控制,所以是线程安全的
* @ClassName: InnerSingletonHolder
* @Description: 静态内部类
* @author anling.song
* @date 2017年7月6日 下午4:30:36
*/
private static class InnerSingletonHolder{
private static InnerStaticClassSingleton instance = new InnerStaticClassSingleton();
}
public static InnerStaticClassSingleton getInstance(){
return InnerSingletonHolder.instance;
}
}
package com.bufoon.test.gof.singleton;
import java.util.HashMap;
import java.util.Map;
/**
* 在高效Java第二版上提到的一种实现方式,
* 因为枚举的一个属性就是代表枚举类自己,
* 所以用来实现单例模式非常简便和通俗易懂;
* @ClassName: EnumSingleton
* @Description: 枚举单例类
* @author anling.song
* @date 2017年7月6日 下午4:33:10
*/
public enum EnumSingleton {
enumInstance;
// 存花式刹停动作分数存储
private Map<String, Integer> map = null;
/**
* Enum构造方法,JVM会保证调用时只初始化一次
*/
private EnumSingleton(){
System.out.println("初始化数据............");
if (map == null) {
map = new HashMap<String, Integer>();
}
map.put("V_TOE_TOE", 10);
map.put("EIGHT_CROSS", 9);
map.put("EAGLE", 8);
map.put("BACKSLIDE", 6);
map.put("PARRAL", 4);
}
public int getScore(String action){
if ("".equals(action) || null == action || map == null) {
return -1;
}
return map.get(action);
}
public static void main(String[] args) {
EnumSingleton e1 = EnumSingleton.enumInstance;
EnumSingleton e2 = EnumSingleton.enumInstance;
// 确认枚举是否只初始化了一次构造方法
System.out.println((e1 == e2) + "|" + (e1 == enumInstance) + "|" + (e2 == enumInstance));
System.out.println("V_TOE_TOE 动作的评分为:" + enumInstance.getScore("V_TOE_TOE") + "分");
}
}
5. 思考
单例模式是一种很好的设计思想,符合设计模式中的单一职责,可以与各工厂模式中的工厂类组合使用;单例模式和静态方法类?感觉在系统中比较常用还是后者,包括spring源码里面的很多工具类都是基于后面这种实现;