设计模式——单例模式

概念

单例设计模式(Singleton Design Pattern) 是一种常见的设计模式,属于创建型设计模式,理解起来也非常简单。一个类只允许创建一个对象,那这个类就是一个单例类,这种设计模式就叫做单例模式。
特点:构造方法私有,提供一个全局访问点。

如何实现

1.饿汉式

饿汉式,instance 静态实例在类加载的时候就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。

/**
 * @Author:
 * @Description: 饿汉式  线程安全
 */
public class SingletonExample {

    private static final SingletonExample instance = new SingletonExample();
    
	private SingletonExample (){}  // 构造方法私有化
	
    public static SingletonExample getInstance() {
        return instance;
    }

}

这种实现方式不支持延迟加载(在真正使用到 instance 的时候,再创建实例),有人可能会觉得提前初始化好比较浪费资源,觉得在使用的时候再去初始化,但其实问题不大,提前初始化好耗时肯定要好过使用的时候再初始化耗时,避免使用的时候初始化导致的性能问题。

2. 懒汉式

懒汉式,instance 实例在使用的时候才会去初始化,是支持延时加载的,但却是线程不安全的。

/**
 * @Author:
 * @Description: 懒汉式  线程不安全
 */
public class SingletonExample {

    private static SingletonExample instance;
    
	private SingletonExample (){}  // 构造方法私有化

    public static SingletonExample getInstance() {
        if (instance == null){
            instance = new SingletonExample();
        }
        return instance;
    }

}

在并发情况下,可能会创建出多个 instance 实例,一个线程进入了 if 条件中,还没有创建好的时候,时间片给了另一个线程,另一个线程此时也会进入 if 条件中。

我们可以给 getInstance() 这个方法加上一把锁,这样的话就变成线程安全的了。

/**
 * @Author:
 * @Description: 懒汉式  线程安全
 */
public class SingletonExample {

    private static SingletonExample instance;

	private SingletonExample (){}  // 构造方法私有化

    public static synchronized SingletonExample getInstance() {
        if (instance == null){
            instance = new SingletonExample();
        }
        return instance;
    }

}

但加锁的缺点也很明显,加锁之后这个方法的并发度很低,如果这个单例类偶尔才会被用到,那问题不大,但是,如果这个单例类经常被用到,那么频繁的加锁、释放锁也会造成不小的系统开销。

3. 双重检测

饿汉式不支持延时加载,懒汉式不支持高并发,加锁又有性能问题,双重检测实现方式既支持延迟加载、又支持高并发。

/**
 * @Author:
 * @Description: 双重检测  线程安全
 */
public class SingletonExample {

    private static SingletonExample instance;

	private SingletonExample (){}  // 构造方法私有化

    public static SingletonExample getInstance() {
        if (instance == null){
            // 此处为类级别的锁
            synchronized (SingletonExample.class){
                if (instance == null){
                    instance = new SingletonExample();
                }
            }
        }
        return instance;
    }

}

有人说这种实现方式也有问题,因为可能会发生指令重排序

当执行 instance = new SingletonExample() 这个一步的时候,实际上 cpu 会执行如下指令:

  1. memory = allocate() 分配对象的内存空间
  2. ctorInstance() 初始化对象
  3. instance = memory 设置 instance 指向刚分配的内存
    在多线程环境下可能会出现问题,SingletonExample 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化,就被另一个线程使用了。这样就会出问题。

要解决这个问题,我们需要给 instance 成员变量加上 volatile 关键字,禁止指令重排序。

private static volatile SingletonExample instance;
但其实只有低版本的 Java 才会有这个问题,我们现在用的高版本的 Java 已经在 JDK 内部解决了这个问题。

4.静态内部类

静态内部类的实现方式类似于饿汉式,但又支持延迟加载。

/**
 * @Author:
 * @Description: 静态内部类  线程安全
 */
public class SingletonExample {

    private static volatile SingletonExample instance;

	private SingletonExample (){}  // 构造方法私有化

    private static class SingletonHolder {
        private static final SingletonExample instance = new SingletonExample();
    }

    public static synchronized SingletonExample getInstance() {
        return SingletonHolder.instance;
    }

}

SingletonHolder 是一个静态内部类,只有在调用 getInstance() 方法时,SingletonHolder 才会被加载、创建 instance,并且是线程安全的。

5. 枚举

枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

/**
 * @Author:
 * @Description: 枚举  线程安全
 */
public class SingletonExample {

	private SingletonExample (){}  // 构造方法私有化

    public static SingletonExample getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton {
        INSTANCE;
        private SingletonExample instance;

        // JVM 保证这个方法绝对只调用一次
        Singleton() {
            instance = new SingletonExample();
        }

        public SingletonExample getInstance() {
            return instance;
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值