单例模式:在软件系统中,某个类的对象只能存在一个
- 频繁创建并且销毁的对象
- 耗时过多或者耗费资源太多,并且需要不断创建的对象(数据源connection)
1 饿汉式
- 类加载时候就会加载该对象,保证线程绝对安全
- 假如类被触发加载了,但实例并没有使用到,因此可能造成内存浪费
class Singleton {
/**1.static 保证类加载时候变量就会初始化
2. 私有构造保证不会从外部创建
3. 提供外部访问方式
*/
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
class Singleton {
// 1. 引用不可被修改,外部直接访问
public static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
}
class Singleton {
public static final Singleton INSTANCE;
// 初始化挪到静态代码块中,效果一样
static {
INSTANCE = new Singleton();
}
private Singleton() {
}
}
2 懒汉式
2.1 线程不安全
- 如果a线程进入了判空处理,是还没来得及创建对象,b线程也进入判空处理,多实例
package com.design.pattern.day02;
public class Demo01 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> System.out.println(Singleton.getInstance())).start();
}
}
}
class Singleton {
public static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
try {
// 模仿对象创建花费的时间
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton();
}
return instance;
}
}
2.2 同步静态方法
- 线程安全,但效率太低
- first-thread创建对象,后续多个thread不用再加锁,可以并行直接返回结果
class Singleton {
public static Singleton instance;
private Singleton() {
}
// 锁的是字节码对象
public static synchronized Singleton getInstance() {
if (instance == null) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton();
}
return instance;
}
}
2.3 Double Check
class Singleton {
// 轻量级锁, 保证不会发生。指令重排序引发的错误
// instance虽然被synchonized修饰,但是是共享变量,指令重排就会发生错误
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
/**
* 同步代码块
* 1. 多个线程可以同时进入这个判空
* 2. 后续每个线程进入同步判空代码块中,发现已经instance不为空了
*/
// 只有前一批进入第一个null判断的线程才是加锁访问的
synchronized (Singleton.class) {
if (null == instance) {
// 假设这里耗费了很长时间,也就不会导致第二个线程进来了
instance = new Singleton();
}
}
}
return instance;
}
}
3 静态内部类
- 懒加载: 外部类装载时内部类不会被装载,调用getInstance(), 内部类才会被装载
- 线程安全:JVM通过类装载机制保证了线程安全
class Singleton {
private Singleton() {
}
public static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4 枚举单例
- 线程安全
- 防止反序列化创建多个对象
- Effective Java 推荐方式
enum SingleTon {
INSTANCE;
public void say() {
System.out.println("ok");
}
}