一、概述
单例模式是设计模式中相对简单且非常常见的一种设计模式,但是同时也是非常经典的高频面试题,相信还是有很多人在面试时会挂在这里。本篇文章主要针对单例模式做一个回顾,记录单例模式的应用场景、常见写法、针对线程安全进行调试(看得见的线程)以及总结。相信大家看完这篇文章之后,对单例模式有一个非常深刻的认识。
文章中按照常见的单例模式的写法,由浅入深进行讲解记录;以及指出该写法的不足,从而进行演进改造。
秉承废话少说的原则,我们下面快速开始
二、定义
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
单例模式是创建型模式。
三、应用场景
- 生活中的单例:例如,国家主席、公司 CEO、部门经理等。
- 在
Java
世界中:ServletContext
、ServletContextConfig
等; - 在
Spring
框架应用中:ApplicationContext
、数据库的连接池也都是单例形式。
四、常见的单例模式写法
单例模式主要有:饿汉式单例、懒汉式单例(线程不安全型、线程安全型、双重检查锁类型、静态内部类类型)、注册式(登记式)单例(枚举式单例、容器式单例)、
ThreadLocal
线程单例
下面我们来看看各种模式的写法。
1、饿汉式单例
饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题。
Spring 中 IOC 容器 ApplicationContext 就是典型的饿汉式单例
优缺点
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅坑不拉屎。
写法
/**
* @author eamon.zhang
* @date 2019-09-30 上午9:26
*/
public class HungrySingleton {
// 1.私有化构造器
private HungrySingleton (){}
// 2.在类的内部创建自行实例
private static final HungrySingleton instance = new HungrySingleton();
// 3.提供获取唯一实例的方法(全局访问点)
public static HungrySingleton getInstance(){
return instance;
}
}
还有另外一种写法,利用静态代码块的机制:
/**
* @author eamon.zhang
* @date 2019-09-30 上午10:46
*/
public class HungryStaticSingleton {
// 1. 私有化构造器
private HungryStaticSingleton(){}
// 2. 实例变量
private static final HungryStaticSingleton instance;
// 3. 在静态代码块中实例化
static {
instance = new HungryStaticSingleton();
}
// 4. 提供获取实例方法
public static HungryStaticSingleton getInstance(){
return instance;
}
}
测试代码,我们创建 10 个线程(具体线程发令枪 ConcurrentExecutor 在文末源码中获取):
/**
* @author eamon.zhang
* @date 2019-09-30 上午11:17
*/
public class HungrySingletonTest {
@Test
public void test() {
try {
ConcurrentExecutor.execute(() -> {
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(Thread.currentThread().getName() + " : " + instance);
}, 10, 10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果:
pool-1-thread-6 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-9 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-10 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-7 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-8 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
...
可以看到,饿汉式每次获取实例都是同一个。
使用场景
这两种写法都非常的简单,也非常好理解,饿汉式适用在单例对象较少的情况。
下面我们来看性能更优的写法——懒汉式单例。
2、懒汉式单例
懒汉式单例的特点是:被外部类调用的时候内部类才会加载。
懒汉式单例可以分为下面这几种写法来。
简单懒汉式(线程不安全)
这是懒汉式单例的简单写法
/**
* @author eamon.zhang
* @date 2019-09-30 上午10:55
*/
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
private static LazySimpleSingleton instance = null;
public static LazySi