设计模式轻松学之01 单例模式
前言
GOF( Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides)联合出版的书中提到的设计模式一共有23种,可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。
单例模式的特点
这一章我们主要介绍创建型模式之单例模式(Singleton Pattern)。它的特点是,保证系统中某个类只有一个实例,并且提供一个全局访问点。在我们开发的系统中ServletContext和Spring框架的ApplicationContext都是单例的,数据库连接池也是单例的。
创建单例对象要保证两点:
1. 构造器私有化
这样其他人就无法通过new
关键字来生成另一个对象。
2. 提供一个全局访问点
通常提供一个使用public static
来修饰的getInstance
方法。
两种单例模式的特点
单例模式主要分为两种,恶汉式和懒汉式。
1. 恶汉式
恶汉式指的是类在被加载的时候就会生成实例,虽然可能会浪费一些空间,但是性能比较好,绝对的线程安全,而且写法简单,如果没有特殊要求的话推荐使用这种方法。
2.懒汉式
而懒汉式单例需要在调用的时候才创建,这种情况导致了可能会出现线程安全的危险,所以处理起来会比较的麻烦,而且效率也会受到影响。
恶汉式单例详解
第一种写法
恶汉式的写法非常简单:
public class HungrySingleton {
// 构造器私有化
private HungrySingleton(){}
// 内部构造实例
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
// 全局访问点
public HungrySingleton getInstance() {
return HUNGRY_SINGLETON;
}
// 其他业务代码...
}
第二种写法
还有一个变种的写法是将内部构造的实例放到静态代码块中来处理,但它们的原理是一样的:
public class HungrySingleton {
// 内部构造实例
private static final HungrySingleton HUNGRY_SINGLETON;
static {
HUNGRY_SINGLETON = new HungrySingleton();
}
// 构造器私有化
private HungrySingleton(){}
// 全局访问点
public HungrySingleton getInstance() {
return HUNGRY_SINGLETON;
}
// 其他业务代码...
}
懒汉式单例详解
有问题的写法
首先我们用最简单的方式来写一个懒汉式单例:
public class LazySingleton {
// 构造器私有化
private LazySingleton(){}
// 内部类实例
private static LazySingleton lazySingleton;
// 全局访问点
public static LazySingleton getInstance() {
// 如果内部类实例没有被初始化,那么进行初始化
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
// 其他业务代码...
}
使用synchronized关键字加锁
以上写法有一个问题,那就是如果多个线程同时进入if判断语句中,就会多次执行lazySingleton = new LazySingleton()
这条语句。为了解决这个问题,需要在getInstance
方法上加锁:
public class LazySingleton {
// 构造器私有化
private LazySingleton(){}
// 内部类实例
private static LazySingleton lazySingleton;
// 全局访问点
// **使用synchronized加锁**
public synchronized static LazySingleton getInstance() {
// 如果内部类实例没有被初始化,那么进行初始化
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
// 其他业务代码...
}
双重检查锁
但是,用 synchronized 加锁,在线程数量比较多情况下, CPU 分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降。为了兼顾线程安全又提升程序性能,我们引入了双重检查锁的单例模式:
public class LazySingleton {
// 构造器私有化
private LazySingleton(){}
// 内部类实例
private static LazySingleton lazySingleton;
// 全局访问点
// **不在getInstance上加锁了**
public static LazySingleton getInstance() {
// 判断内部类实例是否为空
if (lazySingleton == null) {
// 在if内部加锁
synchronized (LazySingleton.class) {
// 第二次判断是否为空
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
// 其他业务代码...
}
双重检查锁也有可能会被阻塞,但是阻塞的地方是在getInstance
方法的内部,而第一种锁阻塞的是整个类的阻塞,因此性能还是有不少的提升的。
内部静态类
那么单例模式还有没有更好的不用加锁的写法呢?还是有的,那就是使用内部静态类的方式:
public class LazyInnerClassSingleton {
// 构造器私有化
private LazyInnerClassSingleton(){}
// 全局访问点
public static final LazyInnerClassSingleton getInstance() {
return InstantHolder.LAZY_INNER_CLASS_SINGLETON;
}
// 额外的内部类
private static class InstantHolder{
// 在内部类中初始化实例对象
private static final LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = new LazyInnerClassSingleton();
}
// 其他业务代码...
}
单例模式小结
以上就是单例模式的全部内容,其实还算是比较简单的,因为它的功能其实也比较单一。我们在开发的时候通常会使用Spring IOC容器,通常可以通过配置来实现单例模式的目的。但是单例模式可以算是面试中比较多的一个问题,了解其中原理也有助于提高我们的核心竞争力。