单例模式是 Java 众多设计模式的一种,也是平时用到较多、适用范围较广的一种设计模式,隶属创建型模式。
什么设计模式?说白了就是问题的解决方案,计算1加到100可以每个数字逐个加,也可以像高斯一样首尾相加乘50,设计模式是对问题的一种优秀的解决方案,所以我们需要来看看单例模式这个答案出现之前,所存在的问题:
假设有一个类 它的对象需要被频繁的使用,所以经常需要创建与销毁,而恰巧该类的对象创建耗时很长, 耗资源很多 ,如此看来太耗费系统性能。
发现了问题之后,大牛们开始讨论对策,首先问题出在了创建对象环节,那么如何才能控制创建对象的数量从而减少开销呢?而这引出了另一个问题,在Java的开发中,有几种创建对象的方式呢??
首先当然是最常用的new关键字
Object object = new Object();
其次是JDK提供的反射机制
try { Object object = Object.class.newInstance(); //Object object = Object.class.getConstructor().newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }
但是可以发现无论是new关键字还是反射机制,调用的都是要创建对象类中的构造方法,所以若想控制创建对象的数量,就要从单例类中的构造对象入手。
单例模式特点1:
所有构造方法私有化
构造方法支持private访问修饰符,也就是构造方法可以进行私有化处理。构造方法私有化处理之后,外界遍不可使用new进行类创建或是利用反射机制进行类加载(忽略setAccessible暴力破解)。
public ClassName{
private ClassName(){}
}
如此一来,所有的外界资源无法通过单例类本身创建对象,但是单例类并不是一个类似于Math的静态工具类,单例类在需要的时候还是需要创建对象的,那么,既然无法通过外界创造对象,又调用不了对象内部的私有方法,该如何返回该类对象呢?
单例模式特点2:
静态方法返回自身对象
静态方法可以通过className.methodName(parameter...)进行调用,无需创造对象,故在所有的单例模式中,都会有一个固定的静态方法用于返回自身对象(此代码实例并非单例模式)。
public ClassName{
private ClassName(){}
public static getInstance(){return new ClassName();}
}
但是如果在静态方法体内直接 new 自身的对象进行return , 每次返回的对象还是不同的,那么新的问题便是如何在方法体中返回同一对象?很简单,通过唯一变量进行返回,由此,产生了两种广为熟知的单例模式
饿汉式单例
之所以叫做饿汉式单例,可以理解为一个苦逼的程序员为了怕自己饿肚子,而提前准备好了四碟菜:老醋,花生,花生老醋,老醋花生。同理,懒汉式单例的核心思想便是:不打无准备之仗。
public class SingletonPattern{
private final static SingletonPattern singletonPattern;
static{
singletonPattern = new SingletonPattern();
}
public SingletonPattern(){}
public static SingletonPattern getInstance(){ return singletonPattern;}
}
懒汉式单例
之所以叫做懒汉式单例,可以理解为一个懒惰的苦逼的程序员为了让自己多休息一会,只有在自己饿的时候才会去买:老醋,花生,花生老醋,老醋花生.........
public class SingletonPattern{
private static SingletonPattern singletonPattern ;
public SingletonPattern(){}
public static SingletonPattern getInstance(){
if(Objects.isNull(singletonPattern))
singletonPattern = new SingletonPattern();
return singletonPattern;
}
}
但是新的问题接踵而至,目前对于单例模式的两种写法,都是建立在单线程的基础之上的,那么在多线程的基础上,两种单例模式的写法能否保证返回的对象单一有效呢?
试想多线程进行单例类访问,率先访问 饿汉式单例 ,由于饿汉式单例的对象创建在类初始加载过程中已经创建,静态方法体中只存在返回值语句,故线程安全。
继而试想多线程进行 懒汉式单例 类访问,模拟时间轴:
Thread1 进入 getInstance ↓
判断if { singletonPattern = new singletonPattern(); } return singletonPattern;
Thread2 进入 getInstance ↑
如果线程1判断if语句成立,进入if语句块,即将要创建类对象的时候,虚拟机突然切换至Thread2,此时由于singletonPattern 变量因为Thread1的中断并没有创建完成,此时仍是 null ,故 Thread2 也会进入if 语句块内进行新对象创建,如此一来,最初始的 懒汉式单例 在多线程的情况下并不安全。
为了解决懒汉式单例的安全问题,又提出了几种多线程下维护线程安全的新写法:
方法锁
为了控制线程的访问唯一,在静态方法上添加锁。
public class SingletonPattern{
private static volatile SingletonPattern singletonPattern ;
public SingletonPattern(){}
public synchronized static SingletonPattern getInstance(){
if(Objects.isNull(singletonPattern))
singletonPattern = new SingletonPattern();
return singletonPattern;
}
}
问题得以解决,但是带来了新的问题,即在多线程的情况下,如果该静态方法访问线程过多,而每个线程都会等待之前的线程释放锁后才会进入方法体,这样会让更多的线程进入堵塞状态,影响执行效率,所以针对这一问题,饿汉式单例 提出了新的解决方案。
局部类锁
改进的静态方法将方法锁换做局部锁,只有在 if 语句判断正确的条件下才会进入锁的争夺,解决了多线程下的拥堵问题,并使用 volatile 关键字,保证了单例变量的线程可见性(此代码实例并非安全的单例模式):
public class SingletonPattern {
private static volatile SingletonPattern singletonPattern;
public SingletonPattern() {}
public static SingletonPattern getInstance() {
if (Objects.isNull(singletonPattern))
synchronized (SingletonPattern.class) {
singletonPattern = new SingletonPattern();
}
return singletonPattern;
}
}
但是这样真的能保证线程的绝对安全吗?
再次试想一下之前的判断场景,因为 if 暴露在 synchronized 关键字之外,得不到线程安全的保护,而多个线程还是可能同时进入 if 的方法体中进行对象创建,虽然对象创建的过程是加锁的,但是还是会创建出不同的对象实例,故需要进一步处理,所以有产生了一种新的线程安全的写法:
public class SingletonPattern {
private static volatile SingletonPattern singletonPattern;
public SingletonPattern() {}
public static SingletonPattern getInstance() {
if (Objects.isNull(singletonPattern))
synchronized (SingletonPattern.class) {
if (Objects.isNull(singletonPattern))
singletonPattern = new SingletonPattern();
}
return singletonPattern;
}
}
在同步代码块内再次判断变量空值,这样既保证了多线程下的安全性问题,又解决了多线程堵塞的情况。
现在有了一种比较完美的 饿汉式单例 之后,来比较一下 饿汉式单例 与 懒汉式单例 的区别:
饿汉式单例与懒汉式单例的区别与特点
- 饿汉式单例在类加载期间完成创建,在虚拟机加载之后,该类实例已经被创建,无论该单例类被不被调用,都会占用部分的内存空间,故适用于内存空间占用量不大、资源消耗不多的情况下的单例模式。
- 懒汉式单例 选择在调用制定静态方法时再选择创建类对象,但是写法相对复杂。
那么有没有更加成熟的解决方案呢?当然有,优秀的程序员们不惧怕任何难题。
静态内部类单例模式
静态内部类有一个很特殊的属性,即只有在调用内部类资源的时候才开始加载内部类
当静态内部类没有被外界或者外部类调用的时候,不会主动加载静态资源
利用这一点,可以写出利用 静态内部类 所构造的新的单例模式:
public class SingletonPattern {
public static SingletonPattern getInstance() {
return InnerClass.SingletonPattern;
}
private static class InnerClass {
private final static SingletonPattern SingletonPattern = new SingletonPattern();
}
}
静态内部类单例模式 结合了以上两种模式的有点,代码简洁,不占用系统资源,又保证了多线程下的安全性,也是目前最为提倡的一种单例模式的书写格式。
生命不息学习不止,这只是单例模式前进的一小步,相信诸位优秀的程序员会在将来呈现出更加优秀的设计模式与设计思想。
-by decmoon