什么是单例模式?
单例模式是一种创建型模式。
单例模式确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,它提供全局的访问方法,用于为系统提供单例对象。
单例模式是一种非常简单的设计模式,由于它是在JVM中有且仅有一个,所以这个单例对象的线程安全问题,是我需要着重关注的,也因此衍生出了多种单例模式的写法。
核心结构
单例模式很简单,具体有如下关注点:
- 私有静态成员变量。
- 全局静态方法,用于提供单例实例化对象。
- 私有构造方法。
代码分析
单例模式有很多种实现方式,我们先写一个最简单的单例模式,懒汉单例模式(Lazy-Singleton):
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/10 0010.
* 单例模式——懒汉模式
*/
public class LazySingleton {
private static LazySingleton lazySingleton;
private LazySingleton() {
}
public static LazySingleton getInstantce() {
if(null == lazySingleton) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
public static LazySingleton getLazySingleton() {
return lazySingleton;
}
}
懒汉模式采用了延迟加载的策略,即什么时候需要我了,我才生产一个对象给你,当然在null == lazySingleton时,如果这个时候并发请求,就会出现线程不安全的问题,所以我们可以使用sychronized内置锁对这个方法进行上锁:
public static sychronized LazySingleton getInstantce() {
if(null == lazySingleton) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
这样加锁,对多线程场景是一种摧残,我们需要不时的获取、释放锁,极大的消耗了性能(试想一下,这是传统的数据库连接),有没有一种不需要上锁的线程安全的写法呢?
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/10 0010.
* 单例模式——饿汉模式
*/
public class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
这种方式俗称饿汉单例模式,为什么说它是线程安全的呢?因为当类被加载时,静态成员变量也随着进行了加载,这个时候单例对象已经被创建,不会出现各种创建单例对象的情况。
忽然间发现,在类加载的时候就在JVM中创建单例对象是一个绝佳的思想,除了饿汉单例模式,还有什么比较装逼的写法呢?
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 单例模式——内部实现
*/
public class InnerSingleton {
private InnerSingleton() {
}
public static InnerSingleton getInstance() {
return Singleton.innerSingleton;
}
private static class Singleton {
private final static InnerSingleton innerSingleton = new InnerSingleton();
}
}
静态内部类实现方式闪亮登场,如果你还不明白静态内部类的作用,或者是各种内部类的作用,Google大法好。
B格有了,我们回归到凡人,写一种自欺欺人的单例写法:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 单例模式——双重校验锁
*/
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton doubleCheckSingleton;
private DoubleCheckSingleton() {
}
public static DoubleCheckSingleton getInstance() {
if (doubleCheckSingleton == null) {
synchronized (doubleCheckSingleton) {
if (doubleCheckSingleton == null) {
doubleCheckSingleton = new DoubleCheckSingleton();
}
}
}
return doubleCheckSingleton;
}
public DoubleCheckSingleton getDoubleCheckSingleton() {
return doubleCheckSingleton;
}
}
其实双重校验锁的写法,就是对之前对方法上锁的一种更细粒度的同步机制。
模式总结
优点
- 提供系统性能,对于需要经常创建、销毁的对象,单例模式无异是一种解脱。
- 严格控制外部调用何时、如何调用。
缺点
- 没有针对接口变成,扩展难度大。
- 违背了单一职责的原则,单例类既承担创建对象的角色,还承担了分配的对象的角色。
适用场景
- 来来回回只需要这一个对象,比如说数据库连接(连接池对数据库的连接只是稍稍做了优化)。
- 对象只提供一个公共访问点,其他资源外部调用无法访问。