单例模式
单例模式的介绍
保证一个类仅有一个实例,并提供用于一个访问它的全局访问点。
单例模式是Java中最简单的设计模式,它的核心在于它能保持全局只有一个这个类的对象,如果外部想要去获取这个类的对象时,不能自己创建,只能通过这个类提供给外部的方法去获取,也正是这个原因,单例模式的类的构造方法一般都是私有的。
五种写法及其优缺点
1.饿汉模式
饿汉模式简单来说就是自己很饿,在类加载的时候就去创建了对象。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
优点:没有加锁,效率很高
缺点:类加载时就初始化了,浪费内存
2.懒汉模式
懒汉模式就是很懒,只有在获取对象的时候才会去创建对象。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:只有在使用的时候才会初始化,节约了资源
缺点:第一次加载的时候需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance方法时都需要线程同步,造成不必要的同步。
3.双重检查模式/DCL
DCL是结合了懒汉模式写的,他相当于是懒汉模式的改良版,既能在调用getInstance的时候才去实例化,又能保证线程安全,最重要的是初始化后调用的时候不再需要加锁。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这个方法里面有两个判空,第一个判空是为了避免不必要的同步,第二层判空是为了在null的时候去创建实例,保证线程安全。如果没有第二个判空,则会出现一个问题:假设我有两个线程AB都在执行getInstance方法,当A执行第一个if的时候被B抢去了,结果B执行第一个if的时候,又被A抢回去了,这是A会创建实例,B也会创建一个实例,这样就违背了单例模式的要求。所以加上了第二个判空。
但是这样会有一个问题,singleton = new Singleton(); 这句会被JVM差分成多条汇编指令,大致执行了三件事:
- 给Singleton实例分配内存
- 调用Singleton()的构造函数,初始化成员字段
- 将sInstance对象指向分配的内存空间
但是由于JVM允许处理器乱序执行,所以第2步和第3步的执行顺序可能会被打乱,这时如果在执行完第3步而第2步还没有执行的情况下,被切换到了另一个线程上,由于此时第3步已经执行,所以SInstance已经非空了,这时直接取走了sInstance就会出错。
这个问题可以通过Volatile关键字实现,这个关键字能保证可见性和有序性,所以此时如果用Volatile修饰Singleton的话,就能保证按照123的顺序执行。就不会出现问题。
静态内部类单例模式
就是将构造方法放入了类的一个静态内部类中,然后如果要获取这个对象的话,就调用getInstance方法,而getInstance方法就去调用静态内部类的静态变量就可以了。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这样的话,第一次加载Singleton类的时候并不会初始化,而是只有当第一次调用getInstance方法,才能让虚拟机加载静态内部类,也就让静态内部类中的单例类的对象能被初始化。这样不仅保证了线程安全,也保证了单例对象的唯一,同时也延迟了单利的实例化。
5.枚举类型
枚举就是个很神奇的东西。枚举的写法也很神奇,就定义一个枚举类然后写入变量就可以了。这样的简洁,避免多次实例化,最重要的是它支持自动序列化。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
总结
单例模式分析
使用这个模式时说明系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
缺点:
- 单例类的扩展有很大的困难
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。
- 如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失