浅析_单例模式

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

1、饿汉式

特点:类一加载,就初始化了!因为类只会加载一次,故不存在线程安全问题。

public class Hungry {

    private static final Hungry HUNGRY = new Hungry();

    private Hungry(){}

    public static Hungry getInstance(){
        return HUNGRY;
    }
}

2、懒汉式(DCL)

特点:延迟加载,当需要调用的时候,再初始化!故多线程下存在线程安全问题。

这个比较经典,所以谈谈其中的细节:

(1)为什么双重校验?
第一重校验,是防止没有必要的加锁,避免影响 get 实例的效率;
第二重校验,是防止加锁的过程中,线程A突然阻塞,其他的线程抢先实例化了,而后面线程A被唤醒,导致对象被二次实例化。

(2)为什么要加 volatile 关键字?
因为对象的初始化,大致可以分为三步:

  • 分配内存空间
  • 执行构造方法,实例化对象
  • 把栈帧里定义的对象指向内存空间(栈中存放的是引用地址,只要指向了,就不为null 了)

那么问题就来了:如果指令重排了,第二步初始化最后执行,然后陷入阻塞。那么会有可能会导致,有线程获取的单例对象,其实没有初始化!

public class Lazy {

    private volatile static Lazy lazy;

    private Lazy(){	}

    // 双重检测锁,懒汉式单例,DCL懒汉式
    public static Lazy getInstance(){
        if(lazy == null){ // 防止非必要加锁
            synchronized (Lazy.class){
                if(lazy == null){ // 防止有线程在加锁的过程中,对单例对象初始化了
                    lazy = new Lazy(); // 不是原子性操作
                }
            }
        }
        return lazy;
    }
}

3、静态内部类

特点:和DCL懒汉式类似,但缺点是 外部无法传递参数 ,具体使用需自行取舍!线程安全是因为它也是利用了类加载的特性。

public class Holder {

    private Holder(){
    }

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    // 延迟加载
    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

4、枚举类

特点:最安全的单例,且 不会被反射破坏单例模式!(因为反射源码里有对对象类型进行判断,是否为枚举类)


package com.feng.single;

import java.lang.reflect.Constructor;

/**
 *  enum 本质也是一个Class 类
 *  枚举类实现单例:
 *      最安全,反射也没有办法破解它的单例。
 */
public enum EnumSingle {
    MY_INSTANCE;

    public EnumSingle getInstance(){
        return MY_INSTANCE;
    }

}

观看源码得知:枚举类才是最安全的单例模式。因为连反射都没办法去构造它的对象,破坏它的单例!

5、总结

以上就是单例的几种实现方式了,就各自的特点来说,DCL懒汉式枚举 是比较适合使用的。

posted @ 2020-08-15 22:15  谨丰  阅读(94)  评论(0编辑  收藏  举报