Java设计模式——单例模式

单例模式常用来保证一个类只能创建出一个实例对象。

单例模式的实现需要三个必要的条件:

  • 构造函数必须是私有的,防止调用方创建对象
  • 通过一个私有的静态变量来存储其唯一实例
  • 需要提供一个公开的静态方法,调用方通过静态方法获取唯一实例

注意:
因为单例类的构造函数是私有的,所以单例类不能被继承。

1、饿汉式

在类加载时创建对象
优点:线程安全,创建对象时不需要通过加锁保证线程安全,性能好
缺点:在对象不被调用时也会创建,浪费内存空间

public class HungrySingleHolder {

    private static final HungrySingleHolder HUNGRY_SINGLE_HOLDER = new HungrySingleHolder();

    /**
     * 构造方法私有化,防止别人创建对象
     * 但可以被反射和序列化破坏
     */
    private HungrySingleHolder(){
    }

    public static HungrySingleHolder getInstance(){
        return HUNGRY_SINGLE_HOLDER;
    }

}

测试代码

public class HungryHolderTest {

    /**
     * 线程个数
     */
    private static final Integer THREAD_SIZE = 5;

    public static void main(String[] args) {
        // 构造方法私有化,创建对象报错
//        HungrySingleHolder hungrySingleHolder = new HungrySingleHolder();

        HungrySingleHolder hungrySingleHolder = HungrySingleHolder.getInstance();
        System.out.println(hungrySingleHolder);

        // 多次创建均是同一个对象,线程安全
//        System.out.println(HungrySingleHolder.getInstance());
//        System.out.println(HungrySingleHolder.getInstance());
//        System.out.println(HungrySingleHolder.getInstance());

        // 通过线程多次创建,返回均是同一个对象,体现线程安全
        ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_SIZE);
        for (int i = 0; i < THREAD_SIZE; i++) {
            threadPool.submit(() -> System.out.println(HungrySingleHolder.getInstance()));
            threadPool.execute(() -> System.out.println(HungrySingleHolder.getInstance()));
        }
        threadPool.shutdown();
    }
}

2、懒汉式

在对象被使用时才会创建
优点:延迟加载,不会造成内存空间浪费
缺点:不能保证线程安全

public class LazySingleHolder {

    private static LazySingleHolder LAZY_SINGLE_HOLDER;

    /**
     * 构造方法私有化,防止别人创建对象
     * 但可以被反射和序列化破坏
     */
    private LazySingleHolder(){
    }

    public static LazySingleHolder getInstance(){
        LAZY_SINGLE_HOLDER = new LazySingleHolder();
        return LAZY_SINGLE_HOLDER;
    }

}

测试代码

public class LazyHolderTest {

    /**
     * 线程个数
     */
    private static final Integer THREAD_SIZE = 5;

    public static void main(String[] args) {
        // 构造方法私有化,创建对象报错
//        LazySingleHolder lazySingleHolder = new LazySingleHolder();

        LazySingleHolder lazySingleHolder = LazySingleHolder.getInstance();
        System.out.println(lazySingleHolder);

        // 多次创建均不是同一个对象,线程不安全
//        System.out.println(LazySingleHolder.getInstance());
//        System.out.println(LazySingleHolder.getInstance());
//        System.out.println(LazySingleHolder.getInstance());
//        System.out.println(LazySingleHolder.getInstance());

        // 通过线程多次创建,返回均不是同一个对象,体现线程不安全
        ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_SIZE);
        for (int i = 0; i < THREAD_SIZE; i++) {
            threadPool.submit(() -> System.out.println(LazySingleHolder.getInstance()));
        }
        threadPool.shutdown();
    }
}

懒汉式优化——双重校验锁

由于懒汉式非线程安全,为了解决这个问题,引入双重校验锁
优点:延时加载,不会造成内存空间浪费,线程安全
缺点:由于加锁,并发性能不如饿汉式,但实际使用中无伤大雅

public class LazySingleCheckLockHolder {

    // volatile防止指令重排
    private static volatile LazySingleCheckLockHolder LAZY_SINGLE_CHECK_LOCK_HOLDER;

    /**
     * 构造方法私有化,防止别人创建对象
     * 但可以被反射和序列化破坏
     */
    private LazySingleCheckLockHolder(){
    }

    // synchronized加到方法上串行,性能差
    public static LazySingleCheckLockHolder getInstance(){
        if (Objects.isNull(LAZY_SINGLE_CHECK_LOCK_HOLDER)){
            // synchronized加到类上,依旧会存在线程不安全
            synchronized (LazySingleCheckLockHolder.class){
                // 双重校验锁,保证线程安全
                if (Objects.isNull(LAZY_SINGLE_CHECK_LOCK_HOLDER)) {
                    LAZY_SINGLE_CHECK_LOCK_HOLDER = new LazySingleCheckLockHolder();
                }
            }
        }
        return LAZY_SINGLE_CHECK_LOCK_HOLDER;
    }

}

LAZY_SINGLE_CHECK_LOCK_HOLDER = new LazySingleCheckLockHolder()并非一个原子操作,实际上在JVM里做了3件事:

  1. 给LAZY_SINGLE_CHECK_LOCK_HOLDER分配内存
  2. 调用LazySingleCheckLockHolder构造完成初始化
  3. 使LAZY_SINGLE_CHECK_LOCK_HOLDER对象的引用指向分配的内存空间(完成这一步LAZY_SINGLE_CHECK_LOCK_HOLDER就不是null了)

但是在 JVM 的即时编译器中存在指令重排序的优化,也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3,也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 LAZY_SINGLE_CHECK_LOCK_HOLDER 已经是非 null 了(但却没有初始化),所以线程二会直接返回 LAZY_SINGLE_CHECK_LOCK_HOLDER。

volatile关键字的作用:禁止指令重排序优化。即在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。

测试代码

public class LazyCheckLockHolderTest {

    /**
     * 线程个数
     */
    private static final Integer THREAD_SIZE = 5;

    public static void main(String[] args) {

        // 构造方法私有化,创建对象报错
//        LazySingleCheckLockHolder lazySingleCheckLockHolder = new LazySingleCheckLockHolder();

//        LazySingleCheckLockHolder lazySingleCheckLockHolder = LazySingleCheckLockHolder.getInstance();
//        System.out.println(lazySingleCheckLockHolder);

        // 单线程场景,多次创建均是同一个对象,线程安全
//        System.out.println(LazySingleCheckLockHolder.getInstance());
//        System.out.println(LazySingleCheckLockHolder.getInstance());
//        System.out.println(LazySingleCheckLockHolder.getInstance());
//        System.out.println(LazySingleCheckLockHolder.getInstance());

        ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_SIZE);
        for (int i = 0; i < THREAD_SIZE; i++) {
            threadPool.submit(() -> System.out.println(LazySingleCheckLockHolder.getInstance()));
        }
        threadPool.shutdown();

    }
}

3、静态内部类

优点:线程安全,支持延时加载,获取对象时不需要加锁。
缺点:第一次加载时不够快

public class InnerSingleHolder {

    private static InnerSingleHolder lazyInnerHolder;

    public static InnerSingleHolder getInstance(){
        return InnerClassHolder.lazyInnerHolder;
    }

    static class InnerClassHolder {

        private static final InnerSingleHolder lazyInnerHolder = new InnerSingleHolder();

    }


}

4、枚举式

单例模式的最佳实现方法,利用枚举的特性来解决线程安全和单一实例的问题,还可以防止反射和反序列化对单例的破坏

public enum EnumSingleHolder {

    /**
     * 枚举式单例对象
     */
    ENUM_SINGLE_HOLDER;

}

测试代码

public class EnumHolderTest {

    public static void main(String[] args) {
        // 利用枚举的特性来解决线程安全和单一实例的问题,还可以防止反射和反序列化对单例的破坏
        EnumSingleHolder a = EnumSingleHolder.ENUM_SINGLE_HOLDER;
        EnumSingleHolder b = EnumSingleHolder.ENUM_SINGLE_HOLDER;
        System.out.println(a == b);
    }
}

5、容器式

优点:适用于需要大量创建单例对象的场景,便于管理
缺点:需要通过双重校验锁保证线程安全

public class ContainerSingleHolder {

    private static final Map<String, Object> CONTAINER_SINGLE_HOLDER = Maps.newHashMap();

    public static Object getInstance(String key) throws Exception {
        try {
            Object value;
            if (!CONTAINER_SINGLE_HOLDER.containsKey(key)){
                synchronized (ContainerSingleHolder.class) {
                    if (!CONTAINER_SINGLE_HOLDER.containsKey(key)) {
                        value = Class.forName(key).newInstance();
                        CONTAINER_SINGLE_HOLDER.put(key, value);
                    }
                }
            }
            return CONTAINER_SINGLE_HOLDER.get(key);
        } catch (Exception e) {
            throw new Exception("执行异常", e);
        }

    }
}

测试代码

public class ContainerHolderTest {

    /**
     * 线程个数
     */
    private static final Integer TREAD_SIZE = 5;

    public static void main(String[] args) throws Exception {

        ExecutorService threadPool = Executors.newFixedThreadPool(TREAD_SIZE);
        for (int i = 0; i < TREAD_SIZE; i++) {
            threadPool.submit(() -> {
                OrderInfo containerSingleHolder = null;
                try {
                    containerSingleHolder = (OrderInfo) ContainerSingleHolder.getInstance(
                            OrderInfo.class.getName());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(containerSingleHolder);
            });
        }
        threadPool.shutdown();

    }
}
public class OrderInfo {
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值