简述
单例模式应该算是最简单的、代码量最少的模式了。
单例模式,英译“独生子”,其目的就是保证该对象在此应用中的唯一性。
目的
在简述中说的对象的唯一性可能还有些模糊,而如果一个对象在应用中使用次数非常多,在每一次的使用中,该对象被一次又一次地创建,这样是完全没有必要的操作,因为既浪费了资源,又增大了系统的开销。
单例模式的存在,正是解决这样的困惑。
大致流程
1. 私有化(private)该类的构造函数,其目的是为了防止其他对象实例化(new)该对象。
2. 在该类中通过new的方式创建一个本类的对象。
3. 最后再将创建的本类对象通过共有方法(public)返回。
UML类图
实现
目前单例模式的实现有饿汉式、饱汉式、加线程锁的饱汉式、双重加锁检查的饱汉式、静态内部类。
下面就一一介绍。
饿汉式
为了防止Hungry对象被其他类多次创建,所以创建这个Hungry对象的时候不能以new的形式创建,因此它的构造方法故意写成了private的;那如何不new出对象就能调用它的方法呢?那它的方法一定是static的形式,然而静态方法只能使用静态成员,所以说mHungry也被写成了static的。
所以饿汉式的基本写法就大致出来了。
public class Hungry {
private static Hungry mHungry = new Hungry();
private Hungry(){}
public static Hungry getInstance(){
return mHungry;
}
public void getName(){
System.out.println("Hungry : get name");
}
}
为了证明确实获取了Hungry对象,我在这个对象中又写了一个getName()方法,如果获取了这个对象,然后这个对象就可以调用getName()这个方法了。
public class Test {
public static void main(String[] args) {
Hungry.getInstance().getName();
}
}
输出如下:
优点:写法简单;线程安全;创建对象无延迟。
缺点:即使没有使用,启动也会创建对象;启动速度慢。
饱汉式(懒汉式)
饱汉式的写法与饿汉式的写法差距只在什么时候创建对象,相比饿汉式,饱汉式由启动即创建对象变成了在第一次调用时才会创建对象。这样一来,大大提高了启动速度,如果一直用不到该对象的话,该类也就一直不会被创建。
但是,如果两个线程同时调用getInstance()方法,则很有可能重复创建该对象。
public class FullBase {
private static FullBase mFullBase = null;
private FullBase(){}
public static FullBase getInstance(){
if (mFullBase == null){
mFullBase = new FullBase();
}
return mFullBase;
}
}
优点:启动速度快;懒加载,使用时才会创建对象。
缺点:线程不安全。
饱汉式(加线程锁)
上面的饱汉式写法虽然优化了启动速度,节约了资源,却也造成了线程不再安全,而这个缺点在多线程操作中是致命的。
如何再保证在多线程下线程的安全呢?相信大多数人的第一反应就是加锁,也就是加一个synchronized关键字。因为这是在写法上最简单的,加完锁的方法就实现了一个线程互斥的效果,当A线程访问的时候,B线程需要等A线程将锁释放后才能进行对该方法的访问,以这样的方式实现了线程安全。
public class FullSync {
private static FullSync mFullSync = null;
private FullSync(){}
public static synchronized FullSync getInstance(){
if (mFullSync == null){
mFullSync = new FullSync();
}
return mFullSync;
}
}
优点:线程安全;懒加载,使用时才会创建对象。
缺点:并发性能由于synchronized变差;执行效率低。
饱汉式(双重加锁检查)
双重加锁检查(Double Check Lock),同样是基于饱汉式的写法,在创建对象的时候先判断该对象是否为空,非空,则直接返回该对象;对象为空,再去创建。在创建对象之前,有一个类似于上一个方法的锁,这是为了保证在多线程下的线程安全,如果为空该对象为空,则创建一个对象,并返回。
在该写法中,一共有两个if(mFullDCL == null),而两个if判断各司其职。第一个if是为了节省资源,提高执行效率面,如果对象已经被创建,那么直接返回即可,不必再执行该方法中的并发操作,造成资源的不必要浪费;第二个if是为了保证线程安全,抛开第一个if不说的话,是不是与加锁后的饱汉式写法是一样的呀?
public class FullDCL {
private static volatile FullDCL mFullDCL = null;
private FullDCL(){}
public static FullDCL getInstance(){
if (mFullDCL == null){
synchronized (FullDCL.class){
if (mFullDCL == null){
mFullDCL = new FullDCL();
}
}
}
return mFullDCL;
}
}
优点:懒加载、线程安全。
缺点:在jdk1.5版本之前线程是不安全的。
Holder模式(静态内部类)
线程安全吗?
安全,因为类的静态成员只能被加载一次,这样就保证了只会有一个实例对象。
这种模式会不会创建时立即被加载?
不会,因为加载一个类时,内部类不会被同时加载。
目前比较推荐这一种写法。
public class SingletonClass {
private static class SingletonHolder{
private static SingletonClass mSingletonClass = new SingletonClass();
}
private SingletonClass(){}
public static SingletonClass getInstance(){
return SingletonHolder.mSingletonClass;
}
}
GitHub
https://github.com/suyichen/DesignPattern
参考文献
https://www.jianshu.com/p/0b6e7131e572
https://www.cnblogs.com/t0000/p/8250686.html
https://blog.csdn.net/sinat_36026521/article/details/81254383