单例模式(各种模式的详细代码介绍)

单例(Singleton)模式的定义:

指一个类只有一个实例,且该类能自行创建这个实例的一种模式

单例模式有 3 个特点:

单例类只有一个实例对象;
该单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点;

分为以下单例模式

1 饿汉式

/**
 * 1.饿汉式
 * 优点:执行效率高,性能高,没有任何的锁
 * 缺点:某些情况下,可能会造成内存浪费
 */
public class HungrySingleton {

    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return  hungrySingleton;
    }
}

2 懒汉式

/**
 * 2 懒汉式 + 双重检查机制
 * 优点:性能高了,线程安全了
 * 缺点:可读性难度加大,不够优雅
 */
public class LazyDoubleCheckSingleton {
    // volatile关键字 防指令重排序的问题 
    private volatile static LazyDoubleCheckSingleton instance;

    private LazyDoubleCheckSingleton(){}

    public static LazyDoubleCheckSingleton getInstance(){
        //检查是否要阻塞
        if (instance == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                //检查是否要重新创建实例
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

测试单例模式 线程安全 测试代码

/**
 * 测试单例模式 线程安全 测试代码
 */
public class TestThread implements Runnable {
    @Override
    public void run() {
        LazySimpleSingletion instance = LazySimpleSingletion.getInstance();
        System.out.println(Thread.currentThread().getName()+":"+instance);
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(new TestThread());
        Thread thread2 = new Thread(new TestThread());
        thread1.start();
        thread2.start();
    }
}


3 内部静态类单例

/**
 * 3 内部静态类单例
 * 避免了内存浪费,线程安全
 * 需要防止反射破坏
 */
public class LazySingleton {
    private LazySingleton() {
        // 防止反射破坏 (以上模式同理)
        if (LazyHolder.INSTANCE != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }
    public static LazySingleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    // 默认不加载 调用时加载
    private static class LazyHolder {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
}

反射破坏单例

/**
 * 反射破坏单例测试类
 * 解决方案 在构造器中 判断该实例是否已经创建,已创建抛出异常
 */
public class Test {
    public static void main(String[] args) throws Exception{
        Class<?> clz= HungrySingleton.class;
        Constructor c = clz.getDeclaredConstructor();
        c.setAccessible(true);
        Object a = c.newInstance();
        Object b = c.newInstance();

        System.out.println(a);
        System.out.println(b);
    }
}

序列化破坏单例

/**
 * 4 序列化破坏单例
 * 一个单例创建对象后,有时候需要将对象序列化写入磁盘,下次使用时再从磁盘中读取对象进行反序列化,
 * 将其转化为内存对象,反序列化后的对象会重新分配内存,即重新创建对象,如果序列化的对象为单例对象,就违背了单例模式
 * 解决方案
 * 编写readResolve方法 返回单例对象
 */
public class SeriableSingleton implements Serializable {

    public  final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(){}

    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }
    /**
     * 编写readResolve方法 防止反序列化破坏单例
     * 查看JDK源码 ObjectInputStream.readObject()中的readObject0()方法
     * 从中找到readOrdinaryObject()方法中有以下代码 2053行
     * [obj = desc.isInstantiable() ? desc.newInstance() : null; ]
     * 在上方我可以发现已经实例化新的对象
     * 接着往下看 2076行 [desc.hasReadResolveMethod()]
     * 以上判断该对象是否有readResolve()方法
     * 如果有调用此方法 返回readResolve里面的对象
     */
    private Object readResolve(){ return INSTANCE;}

}

反序列化测试类

/**
 * 反序列化测试类
 */
public class Test {
    public static void main(String[] args){
        SeriableSingleton s1 = SeriableSingleton.getInstance();
        SeriableSingleton s2;

        try {
            //序列化
            //把内存中对象的状态转换为字节码的形式
            //把字节码通过IO输出流,写到磁盘上
            //永久保存下来,持久化
            FileOutputStream out = new FileOutputStream("SeriableSingleton");
            ObjectOutputStream oOut = new ObjectOutputStream(out);
            oOut.writeObject(s1);
            oOut.flush();
            oOut.close();

            //反序列化
            //将持久化的字节码内容,通过IO输入流读到内存中来
            //转化成一个Java对象
            FileInputStream inputStream = new FileInputStream("SeriableSingleton");
            ObjectInputStream oIn = new ObjectInputStream(inputStream);
            s2 =(SeriableSingleton) oIn.readObject();
            oIn.close();

            System.out.println(s1);
            System.out.println(s2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // todo 关闭流
    }
}

5 枚举式单例

/**
 * 4 枚举式单例
 *  1是饿汉式单例模式的实现 反编译该class 可看到 静态代码块给 INSTANCE 赋值
 *  2不会被反序列化破坏
 *      同理 查看JDK源码 ObjectInputStream.readObject()中的readObject0()方法
 *      从中找到 readEnum()方法 中的代码2011行
 *      [Enum<?> en = Enum.valueOf((Class)cl, name);]
 *      可以看出 枚举类型通过类名和类对象找到一个唯一的枚举对象,因此枚举对象不可能被类加载器加载多次.
 *  3不会被反射破坏
 *      在 JDK源码中 Constructor.newInstance方法中
 *      不允许反射创建枚举实例
 */
public enum EnumSingleton {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance(){return INSTANCE;}
}

6 容器式单例

/**
 * 容器式单例
 *      适用于大量创建单例对象
 *      线程不安全
 */
public class ContainerSingleton {

    private ContainerSingleton() {}

    private static final Map<String, Object> IOC = new ConcurrentHashMap<>();

    public static Object getInstance(String className) {
        Object instance = null;
        synchronized (IOC) {
            if (!IOC.containsKey(className)) {
                try {
                    instance = Class.forName(className).newInstance();
                    IOC.put(className, instance);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return instance;
            } else {
                return IOC.get(className);
            }
        }
    }
}

7 ThreadLocal单例

/**
 * ThreadLocal单例
 *  保证线程内部的全局唯一,当前线程唯一
 *  其他单例模式,为了线程安全,给方法加锁,以时间换空间
 *  ThreadLocal将所有对象全部放在ThreadLocalMap中,为每个线程提供一个对象,实际上是以空间换时间来实现线程隔离的
 */
public class ThreadLocalMapSingleton {
    private static final ThreadLocal<Map<String,Object>> THREAD_LOCAL_INSTANCE;

    static {
        THREAD_LOCAL_INSTANCE = new ThreadLocal<Map<String, Object>>() {
            @Override
            protected Map<String, Object> initialValue() {
                return new HashMap<>(10);
            }
        };
    }

    private ThreadLocalMapSingleton() {}

    public static Map<String, Object> getInstance() {
        return THREAD_LOCAL_INSTANCE.get();
    }
}

总结

饿汉式(静态块的饿汉式)
		内存浪费
懒汉式
	简单懒汉式 性能问题
	双重检查锁 不优雅
	静态内部类 可被发射破坏
注册式
	枚举式 原理就是静态块的饿汉式
	容器式 spring采用
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值