Design Pattern [1] —— 单例模式 Singleton
单例模式:一个类只能构造一个实例对象("构造器私有")
场景:
Windows任务管理器、回收站
项目中,配置文件的类,一般只有一个对象
网站的计数器、时钟
数据库连接池
Servlet
Spring中的Bean(缓存中取bean很快,减少jvm垃圾回收)(当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象)
好文:https://zhuanlan.zhihu.com/p/33102022 (本文还需要进一步根据面经深化)
我的Github里有源码,可以clone下来自己跑下:https://github.com/Yang2199/Design-Pattern/tree/master/src
1)饿汉式单例
==》上来直接new对象,所有类实例化。坏处是:大量浪费不必要的资源(因为很多类 不需要实例化)
public class Hungry {
private Hungry(){} //构造函数
private final static Hungry HUNGRY = new Hungry();//直接实例化,new出对象
public static Hungry getInstance(){
return HUNGRY;
}
}
2)懒汉式单例
==》懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。
线程不安全:多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,
因此需要加锁解决线程同步问题( Synchronized 同步锁来修饰 getInstance 方法)
public class Singleton {
private Singleton() {} //单例=》私有构造器
private static Singleton instance = null; //单例对象
public static Singleton getInstance() { //调用getInstance方法才会开始构造对象
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
但是这种方法不符合线程安全:
public class Singleton {
private Singleton() {
System.out.println(Thread.currentThread().getName()); //构造时候打印线程名
} //私有构造函数
private static Singleton instance = null; //单例对象
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
for(int i =0;i<10;i++){
new Thread( ()->{Singleton.getInstance();} ).start(); //1.启动新线程 2.lambda表达式
}
}
}
可能会出现构造了很多个的情形
方法一:双重锁检测 DCL
==》首先将类加同步锁(syn),但是new语句不是原子操作,所以对了类的实例加volatile锁(可见性)
第一把锁:synchronized锁
public static Singleton getInstance() {
if (instance == null) { //外层判断
synchronized (Singleton.class) { //将Singleton类 加锁
if (instance == null) { //原有判断
instance = new Singleton();
// 上面这行 不是原子操作:
// 1.分配内存空间 2.执行构造方法,初始化对象 3.把这个对象指向这个空间
}
}
}
return instance;
}
//加上两层:一层if、一层锁
//这样就能保证单例在多线程下的唯一性
//双重检查模式:DCL (Double Check)
第二把锁:volatile锁
private volatile static Singleton instance = null; //由于instance = new Singleton()的非原子性(new对象3步),所以需要volatile保证强制同步一致
volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。
但是,还是可以用反射来破坏DCL:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Singleton instance = Singleton.getInstance();
Constructor<Singleton> declaredConstructor =Singleton.class.getDeclaredConstructor(null); //通过反射获取declaredConstructor这个构造器
declaredConstructor.setAccessible(true);
Singleton instance2 = declaredConstructor.newInstance();
System.out.println(instance.hashCode() );
System.out.println(instance2.hashCode() );
}
实现单例模式,3种方法对比:
方法二:用静态内部类
public class Holder {
private Holder(){}
public static Holder getInstance() {return InnerClass.HOLDER;}
private static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
这个方法也能被反射破坏单例的唯一性
方法三:枚举
==》直接把类名前的class替换成enum就好了,因为枚举无法反射
枚举:(通过查看源码可知,枚举时,无法反射。)
public enum EnumSingleton { //这里是enum而不是class
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
测试:
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingleton instance1 = EnumSingleton.INSTANCE.getInstance();
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingleton instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
会显示:
异常:枚举无法创造反射
符合预期。