单例模式
定义:保证一个类仅有一个实例,并提供一个全局的访问点
使用场景:
- 想确保任何情况下都绝对只有一个实例
- 当你想控制实例数目,节省系统资源的时候
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)
- 设置全局访问点,严格控制访问
缺点:
- 没有接口,不能继承
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化
注意事项:
- 私有构造器
- 线程安全
- 延迟加载(在第一次需要的时候再创建实例,避免不必要的内存浪费)
- 序列化和反序列化安全(需要在单例类里,添加readResolve()方法)
- 防止反射攻击(解决办法:在私有构造器里面,添加判断,抛出异常)
不同版本的单例模式:
1.饿汉式:在类加载的时候就new出一个可供全局的实例
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){ // 防止反射创建新的实例
if(hungrySingleton != null){
throw new RuntimeException("单例模式禁止反射调用");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
private Object readResolve(){ //序列化和反序列化安全
return hungrySingleton;
}
}
2.懒汉式:延迟加载,在第一次需要的时候,创建实例
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){
if(lazySingleton != null){
throw new RuntimeException("单例模式禁止反射调用");
}
}
public synchronized static LazySingleton getInstance(){ // 线程安全
if (lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
3.懒汉式之双重校检锁:
public class LazyDoubleCheckSingleton {
/**
* 注意实例变量要加volatile修饰,防止指令重排
* 新建一个对象实例有三个步骤:
* 1.分配内存给这个对象
* 2.初始化对象
* 3.设置 lazyDoubleCheckSingleton 指向刚分配的内存地址
* 但是 JVM 可能会对编译的指令进行重排序,如:上面的三个步骤顺序可能变为 1->3->2
* 当一个线程 Thread1 新建实例时,执行到上面重排序的顺序步骤 3 (此时还未执行2,即还没有初始化)
* 而另一个线程 Thread2 调用 getInstance() 方法时,发现 lazyDoubleCheckSingleton 已经创建,不为空
* 所以 Thread2 就会引用 lazyDoubleCheckSingleton ,但此时实例还未初始化,会报错
*/
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(){
if(lazyDoubleCheckSingleton != null){
throw new RuntimeException("单例模式禁止反射调用");
}
}
public static LazyDoubleCheckSingleton getInstance(){
if (lazyDoubleCheckSingleton == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazyDoubleCheckSingleton == null){
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
//1.分配内存给这个对象
//2.初始化对象
//3.设置 lazyDoubleCheckSingleton 指向刚分配的内存地址
}
}
}
return lazyDoubleCheckSingleton;
}
}
4.枚举类单例模式,推荐使用
public enum EnumInstance {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance(){
return INSTANCE;
}
}
5.静态内部类,推荐使用
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton(){
if(InnerClass.staticInnerClassSingleton != null){
throw new RuntimeException("单例模式禁止反射调用");
}
}
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
}
6.容器式单例模式
public class ContainerSingleton {
private ContainerSingleton(){
}
private static Map<String,Object> singletonMap = new HashMap<String, Object>();
public static void putInstance(String key,Object instance){
if (StringUtils.isNotBlank(key) && instance != null){
if(!singletonMap.containsKey(key)){
singletonMap.put(key,instance);
}
}
}
public static Object getInstance(String key){
return singletonMap.get(key);
}
}
7.使用 ThreadLocal 的单例模式
/**
* ThreadLocal 多个线程之间是不同的实例,但是线程里面实例唯一
**/
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal = new ThreadLocal<ThreadLocalInstance>(){
@Override
protected ThreadLocalInstance initialValue() {
return new ThreadLocalInstance();
}
};
private ThreadLocalInstance(){
}
public static ThreadLocalInstance getInstance(){
return threadLocalInstanceThreadLocal.get();
}
}
源码用处:
- jdk1.8 的 Runtime类(饿汉式):private static Runtime currentRuntime = new Runtime();
- jdk1.8 的 Desktop(容器式)
- spring 中的 bean 的作用域 singleton
- mybatis 中的 ErrorContext 类 (用到了ThreadLocal类型的单例模式)