什么是单例模式?
概念:23种设计模式之一,通过单例模式的方法创建的类在整个应用程序中只有一个实例。
核心思想:构造方法私有化
实现方式
单利模式的实现方式有很多,在此简单介绍以下几种写法
- 饿汉式
- 懒汉式
- 双重检测锁模式
- 静态内部类
- 枚举类
代码示例
饿汉式:类加载的时候就创建该类的唯一实例对象,天生线程安全。无论该对象是否被使用,在类加载的时候都会被创建,一定程度上导致内存资源的浪费。
//饿汉式单例
public class Hungry {
private Hungry() {
}
private static final Hungry HUNGRY_SINGLETON = new Hungry();
public static Hungry getInstance(){
return HUNGRY_SINGLETON;
}
}
懒汉式:实例对象被使用时才会被创建,实现了内存资源的合理分配。但多线程并发下会破坏这种单例,即线程不安全。
//懒汉式单例
public class Lazy {
private Lazy() {
}
private static Lazy lazySingleton = null;
public static Lazy getInstance() {
if (lazySingleton == null) {
lazySingleton = new Lazy();
}
return lazySingleton;
}
}
为什么上述懒汉式单例是线程不安全的呢,如下代码,在Lazy类的无参构造中加入一行打印并引入多线程场景进行测试
//懒汉式单例
public class Lazy {
private Lazy() {
System.out.println("线程:"+Thread.currentThread().getName()+"创建了对象");
}
private static Lazy lazySingleton = null;
public static Lazy getInstance() {
if (lazySingleton == null) {
lazySingleton = new Lazy();
}
return lazySingleton;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Lazy instance = lazySingleton.getInstance();
System.out.println(instance);
}).start();
}
}
}
运行结果:有5个线程执行了Lazy构造方法创建了5个实例对象(就本次运行结果而言),不符合单例模式的设计。那怎样解决这个问题,实现既能合理的分配内存资源又可以保证线程安全呢?于是就有了下面我们要讲的 双重检测锁模式
D:\installPath\Java\jdk1.8.0_121\bin\java.exe "-javaagent:D:\installPath\IntelliJ IDEA
线程:Thread-0创建了对象
线程:Thread-4创建了对象
线程:Thread-3创建了对象
线程:Thread-1创建了对象
线程:Thread-2创建了对象
com.kang.singleton.Lazy@34a705cd
com.kang.singleton.Lazy@34a705cd
com.kang.singleton.Lazy@56d4b179
com.kang.singleton.Lazy@56d4b179
com.kang.singleton.Lazy@15c05ae4
com.kang.singleton.Lazy@45b30948
com.kang.singleton.Lazy@45b30948
com.kang.singleton.Lazy@14f482d7
com.kang.singleton.Lazy@14f482d7
com.kang.singleton.Lazy@14f482d7
Process finished with exit code 0
双重检测锁模式: 也就是我们所说的DCL懒汉式
//双重检测锁模式
public class DCL {
private DCL() {
}
//因为 new DCL();不是一个原子操作,这里使用volatile修饰dclSingleton 防止指令重排
private volatile static DCL dclSingleton = null;
public static DCL getInstance() {
if (dclSingleton == null) {
synchronized (Lazy.class) {
if (dclSingleton == null) {
dclSingleton = new DCL();//不是一个原子操作
}
}
}
return dclSingleton;
}
}
引入同样的多线程测试场景进行测试
//双重检测锁模式
public class DCL {
private DCL() {
System.out.println("线程:" + Thread.currentThread().getName() + "创建了对象");
}
//因为 new DCL();不是一个原子操作,这里使用volatile修饰dclSingleton 防止指令重排
private volatile static DCL dclSingleton = null;
public static DCL getInstance() {
if (dclSingleton == null) {
synchronized (Lazy.class) {
if (dclSingleton == null) {
dclSingleton = new DCL();//不是一个原子操作
}
}
}
return dclSingleton;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
DCL instance = dclSingleton.getInstance();
System.out.println(instance);
}).start();
}
}
}
运行结果:程序执行只创建了一个实例,符合预期结果(此处暂不考虑Java反射技术对单例模式的影响)
D:\installPath\Java\jdk1.8.0_121\bin\java.exe "-javaagent:D:\installPath\IntelliJ IDEA
线程:Thread-0创建了对象
com.kang.singleton.Lazy@6b047ec
com.kang.singleton.Lazy@6b047ec
com.kang.singleton.Lazy@6b047ec
com.kang.singleton.Lazy@6b047ec
com.kang.singleton.Lazy@6b047ec
com.kang.singleton.Lazy@6b047ec
com.kang.singleton.Lazy@6b047ec
com.kang.singleton.Lazy@6b047ec
com.kang.singleton.Lazy@6b047ec
com.kang.singleton.Lazy@6b047ec
Process finished with exit code 0
静态内部类: 在类的一个生命周期中静态代码块只在类加载时执行一次,可以满足单例模式的设计要求(此处暂不考虑Java反射技术对单例模式的影响)
//静态内部类
public class Singleton {
private Singleton() {
}
//提供一个公开的方法作为访问该唯一实例的入口
public static Singleton getInstance(){
return Test.SINGLETON;
}
//创建静态内部类Test
public static class Test {
private static final Singleton SINGLETON = new Singleton();
}
}
枚举类: 枚举类型是Java 5中新增特性的一部分,也是类(class)的一种,自带单例模式,且可以防止Java反射技术对单例模式的破坏
//枚举类(enum)
public enum Enumeration {
INSTANCE;
public static Enumeration getInstance(){
return Enumeration.INSTANCE;
}
}