单例模式(Singleton Partten)
定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
实现:
单例模式是最简单的一种设计模式,它的通过将类本身构造方法私有,杜绝外部类通过new的方式生成实例对象的操作。既然无法被new,这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
我所知道的实现方式共有五种
1.饿汉方式,类加载时已经将对象创建,不存在线程问题。
特点:线程安全、但不延迟加载
public class SingleTon{
private static SingleTon singleTon = new SingleTon();//静态私有的实例
private SingleTon(){}//私有的构造方法
public static SingleTon getInstance(){ return singleTon; }//静态公有的实例获取方法
}
2.懒汉方式 ,只有getInstance()方法被调用时才回创建对象,但多线程调用时,线程不安全
特点 : 线程不安全、延迟加载
public class SingleTon{
private static SingleTon singleton = null;//静态私有的实例
private SingleTon(){}//私有的构造方法
public static SingleTon getInstance() { //静态公有的实例获取方法
if(singleton == null){
singleton = new SingleTon();
}
return singleton;
}
}
3.双重检查(懒汉优化)
线程安全、延迟加载
public class SingleTon {
private SingleTon() {}//私有的构造方法
private static volatile SingleTon singleton = null; //静态私有的实例
public static SingleTon getSingleTon() { //1 静态公有的实例获取方法
if (singleton == null) { //2
synchronized (SingleTon.class){ //3
if(singleton == null){ //4
singleton = new SingleTon(); //5
}
}
}
return singleton; //6
}
}
这里的volatile并不是为了保证可见性的,而是为了防止指令重排造成返回的 instance 不正确的情况发生。
看line 5代码: instance = new Single ();对象的创建实际包含一下几步:
1. 类是否已经加载,没加载就加载类 ; 2. 申请内存; 3 初始化内存(置0,null等等);4. 执行构造方法,初始化对象; 5. 将生成的对象赋给引用
问题就在于这几步指令是可能重排的,比如1 2 3 4 5,变成1 2 3 5 4。也就是说还没有执行构造方法将这个对象的属性初始化,各个属性都是默认值,就将这个对象赋给了引用instance了。假设有个线程A发生了上面说的情况,生成对象时执行了1 2 3 5步,这时候有个线程B执行line2发现instance不为null了,于是直接执行line6将还未执行构造方法的对象返回。因此为了万无一失,还是要使用volatile的,防止生成对象时第4步和第5步顺序颠倒。
4.静态内部类 ,外部类加载时并不需要立即加载内部类,且当调用getInstance()时,方法返回的是内部类的实例,这样可以保证线程安全(不明白的可以看这篇:https://blog.youkuaiyun.com/mnb65482/article/details/80458571)
特点:线程安全、延迟加载(推荐)
public class SingleTon{
private SingleTon() {}//私有的构造方法
private static class SingleTonHoler {
private static SingleTon singleton = new SingleTon();//静态私有的实例
}
public static SingleTon getsingleton() {//静态公有的实例获取方法
return SingleTonHoler.singleton;
}
}
5.枚举 (推荐)
特点:本身就是单例、可避免反射和反序列化、但不延迟加载
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
前四种方式都无法避免通过反射和反序列化产生多实例对象的问题,而枚举就可以很好的避免,因为枚举本身就是单例,通过反射获取会抛出异常,通过反序列化也无法获取实例对象。
总结:
单例模式的使用可以确保当前虚拟机中单例类只有一个实例化的对象,对于当需要频繁创建和销毁的对象时,使用单例模式,可极大的节省系统资源,同时单例类只有一种访问方式,可以优化和共享资源访问。但滥用单例可能会出现问题,之前用过别人写的单例工具类(solrClient),结果发现使用这个工具类提交数据时solr服务端出异常,后来改成多例,solr服务端就再也没出现过异常。
适用场景:
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1.需要频繁实例化然后销毁的对象。
2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3.有状态的工具类对象。
4.频繁访问数据库或文件的对象。