前言
本文主要内容
单例模式(Singleton Pattern)是一个比较简单的模式,其定义如下: Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。) -设计模式之禅 第2版
简单的说就是:保证一个类在各种情景下,都只有一个实例化对象。
实现
饿汉模式
最简单的一种方式,对外界关闭创建与修改。使用 static 与 final 修饰既保证了一个类只有一个实例化变量,又保证了线程安全(静态常量在准备阶段完成了初始化)
public class SingletonDemo {private static final SingletonDemo instance = new SingletonDemo();private SingletonDemo(){}public static SingletonDemo getInstance(){return instance;}}
这种方式实现简单,即时使用,但是创建就比较慢了。对创建一个类的引用变量来讲,影响极小,但是当创建多个类的引用类型变量时,就很明显了。可以换种方式,当使用时才进行实例化
懒汉模式
或者可以叫延迟加载?只有在获取实例时才进行实例化并返回对象。先看一个demo
Demo
public class IdlerPattern {private IdlerPattern instance = null;private IdlerPattern(){}public static IdlerPattern getInstance(){if (instance == null) {instance = new IdlerPattern();}return instance;}}
代码如上,看起来实现了需求。但是在多线程环境下,以上代码可能会创建多个实例。比如,设某一时间有两个线程,分别为 t1 ,t2.
- t1 调用 getInstance 方法,进行判断,instance == null 为true。运行到 instance = new IdlerPattern();
- 此时,还没有执行 instance = new IdlerPattern(); 时间片使用完,进入等待队列
- t2 调用 getInstance 方法,进行判断,instance == null 为true。运行到 instance = new IdlerPattern();创建实例成功,返回 instance。t2结束
- t1 继续执行,创建实例,返回 instance
此时, 就有了两个实例。不满足需求
锁住整个方法
可以直接在方法中上锁,这样即使线程时间片使用完,另一个线程也无法进入该方法
synchronized (IdlerPatternUseInternalLock.class){if(null == instance){instance = new IdlerPatternUseInternalLock();}}
但是,这样有个问题,对于读取时,因为并不会改变数据信息,因此多个线程一起访问也没有关系,但是这里的 synchronized 连读也一起锁定了,所以,可以进行优化下,减小 synchronized 的粒度
双重检查缩小粒度
通过判断 instance 是否为空,来确定是读还是写
if(null == instance){synchronized (DoubleCheckLocking.class){if(null == instance){instance = new DoubleCheckLocking();}}}
在同步代码块中需要再判断一次,避免在进入时,刚好有线程实例化出一个对象。不过这种方式仍有问题,如果 cpu 为多核心,那么如果线程在不同核心上,可能 instance 信息同步不及时,也就是出现可见性问题。这里可以通过禁用缓存来解决。使用 volatile 来修饰 instance;
package cn.zjm404.stu.dp.creat.singleton;public class DoubleCheckSingleton {private static volatile DoubleCheckSingleton instance = null;public static DoubleCheckSingleton getInstance(){if(instance == null){synchronized (DoubleCheckSingleton.class){if(instance == null){instance = new DoubleCheckSingleton();}}}return instance;}}
静态内部类
实现线程安全的延迟加载单例模式,除了用锁以外,还可以考虑使用下内部类的语言特性.内部类只有在被访问的时候才会被加载,而不是随着类的实例化而一同加载,因此,可以借用这个来实现延迟加载.让内部类使用饿汉模式即可
public class StaticInnerClass {/*** 用作测试,统计实例数量*/private static AtomicInteger num = new AtomicInteger(0);private StaticInnerClass(){System.out.println(num.incrementAndGet());};private static class InnerClass{final static StaticInnerClass INSTANCE = new StaticInnerClass();}public static StaticInnerClass getInstance(){return InnerClass.INSTANCE;}}
使用枚举类实现
public enum EnumSingleton {INSTANCE;private EnumSingleton(){}}
单例模式 VS 静态类
通过上述描述,让一个类只有一个实现,那么完全可以不使用对象,而是使用静态类,那么什么使用时候单例,什么时候使用静态类呢?当考虑延迟加载时,或者要维护属性时,使用单例,除此之外,完全可以不使用对象,改用静态类
