单例模式
一、什么是单例模式
所谓单例就是确保程序中某一个类只有一个实例,并且自行实例化,同时向系统提供这个实例。
单例模式的三个要点:
1、一个类只有一个实例;
2、必须自行创建这个实例;
3、必须向系统提供这个实例;
二、为什么使用单例模式
在软件开发过程中,有些实例的创建非常消耗资源,初始化时间较长,如数据库连接池、一些工具类。秉承节约资源、提升性能的原则,创建出一个实例,提供全局使用。
三、单例模式使用场景
比如 OkHttp 工具类,初始化OkHttpClient实例,供全局使用。
四、单例模式的结构
7.png单例模式结构比较简单,一个类,一个私有静态实例,一个获取实例的公有方法。
五、代码示例
5.1、饿汉式
public class OkHttpUtil {
private static OkHttpUtil instance = new OkHttpUtil();
private OkHttpUtil(){
}
public static OkHttpUtil getInstance() {
return instance;
}
}
饿汉式在类加载的时候就创建,不管用不用,先创建了再说,属于饥不择食。
缺点:如果一直没被使用,便浪费了空间,属于典型的空间换时间。
优点:每次调用的时候,不需要再判断,节省了运行时间,适合常用实例。
5.2、懒汉式(非线程安全)
public class OkHttpUtil {
private static OkHttpUtil instance;
private OkHttpUtil(){
}
public static OkHttpUtil getInstance() {
if (instance == null){
instance = new OkHttpUtil();
}
return instance;
}
}
懒汉式声明一个静态对象,在第一次调用时初始化。
缺点:第一次调用才初始化,如果初始化过程较长,则程序反应稍慢。同时多线程环境下,因为线程非安全,可能会导致多个实例的情况。
5.3、懒汉式(线程安全)
public class OkHttpUtil {
private static OkHttpUtil instance;
private OkHttpUtil(){
}
public static synchronized OkHttpUtil getInstance() {
if (instance == null){
instance = new OkHttpUtil();
}
return instance;
}
}
懒汉式(线程安全)弥补了懒汉式(非线程安全),多线程环境下,线程不安全问题。
缺点:懒汉式(线程安全)使用了 synchronized 会带来性能负担。
优点:懒汉式使用时,才创建实例,节省资源;适用于单例用的不多,但单例比较复杂,加载和初始化需要消耗大量的资源场景。
5.4、双重校验锁 (DCL)
/**
* 注意此处使用的关键字 volatile,它保证了可见性
* 被volatile修饰的变量的值,将不会被本地线程缓存,
* 所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
*/
public class OkHttpUtil {
private static volatile OkHttpUtil instance;
private OkHttpUtil(){
}
public static OkHttpUtil getInstance() {
if (instance == null){
synchronized(OkHttpUtil.class){
if(instance == null){
instance = new OkHttpUtil();
}
}
}
return instance;
}
}
双重校验锁的方式,相较于 懒汉式(线程安全)方式 ,如果程序可以接受synchronized 带来的性能负担,则单例可以使用懒汉式(线程安全);如果对性能有更高的要求,则使用双重校验锁方式,在保证线程安全的前提下,又可以使性能不太受影响。
双重校验锁的两次实例为空判断;第一次是为了不必要的同步;第二次是当 instance为空时,才创建。
使用了同步锁,为什么还需要第二次为空判断??例如当线程A、线程B同时调用 getInstance()方法,并且同时执行第一次为空判断逻辑;如果是第一次实例未初始化,则获取到的 instance 都为null,继续执行下一步,线程A先获取到同步锁,并初始化实例,此时 instance不为空,线程A释放同步锁;线程B获取到同步锁,如果没有第二次 instance为空判断,则会再次对instance初始化,则造成多次实例化,就不再是单例了。
缺点:代码相较于其他方式,代码较复杂,有一定的性能损耗。
优点:相较于懒汉式(线程安全)方式,即保证了线程安全的前提下,又使性能不受很大影响。
六、总结
针对单例模式的应用,常用的方式是,饿汉式、懒汉式(线程安全)、双重校验锁;其他的静态内部类方式、枚举方式、使用容器方式就不再说明了。现将常用的方式,总结信息如下:
1、饿汉式:对于使用频率很高的单例,推荐使用该方式;
2、懒汉式(线程安全):对于使用频率不高,保证线程安全的前提下,并且可以接受 synchronized 带来的性能负担,则可以选择使用该方式。
3、双重校验锁:即保证了线程安全的前提下,又使性能不受很大影响(常用推荐该方式)。