单例设计模式-容器单例

本文详细解析了容器单例模式的实现原理,通过代码演示如何使用Map管理多个单例对象,探讨了其线程安全问题及适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于容器的单例模式,与享元模式类似,我们也可以使用容器单例模式,来管理多个单例对象,那我们通过coding,debug,

讲解的方式来学习一下
package com.learn.design.pattern.creational.singleton;

import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;

/**
 * 容器的单例类
 * 那这个类实现起来非常简单
 * 我们来操作一下
 * 
 * 
 * 
 * @author Leon.Sun
 *
 */
public class ContainerSingleton {

	/**
	 * 我们没有写他的private构造器
	 * 这样我们直接调用静态方法
	 * 这种public的方法
	 * 就OK了
	 * 然来到Test里面
	 * 
	 * 
	 */
    private ContainerSingleton(){

    }
    /**
     * 首先声明一个Map
     * 我们的KEY使用一个String
     * value就用Object
     * 这个叫singletonMap
     * new一个HashMap
     * 这个泛型也写上
     * 那这个map就写好了
     * 本身这个Map作为缓存的话
     * 要往里放对象
     * 也好取对象
     * 那很简单的
     * 
     * 
     */
    private static Map<String,Object> singletonMap = new HashMap<String,Object>();

    /**
     * putInstance两个参数
     * 一个是key
     * 另外是instance
     * 因为它是一个map
     * 可以管理很多对象
     * 然后做一个简单的判断
     * 
     * 
     * @param key
     * @param instance
     */
    public static void putInstance(String key,Object instance){
    	/**
    	 * 用blank来判断key
    	 * 并且instance不为null
    	 * 当这种情况下
    	 * 我们才会往里边放
    	 * 那在放之前呢
    	 * 我们还要用map来判断一下
    	 * 
    	 * Thread0进到这里面他开始判断
    	 * 判断肯定正常
    	 * key传的是object
    	 * value也是一个object
    	 * 注意它是@420
    	 * 
    	 * 
    	 */
        if(StringUtils.isNotBlank(key) && instance != null){
        	/**
        	 * singletonMap.containsKey取反
        	 * 当不包含这个key的时候
        	 * 我们才会真正的往里放
        	 * put key和instance
        	 * 那所谓的容器单例呢
        	 * 就是通过这个map呢去实现单例对象的一个容器
        	 * 那这里是保证key的合法性和唯一性
        	 * 那接下来也很简单
        	 * 
        	 * 进入到这里肯定是不存在的
        	 * 因为map里面的size还是0
        	 * 
        	 * 
        	 */
            if(!singletonMap.containsKey(key)){
            	/**
            	 * 这一行先不动
            	 * 切另一个线程
            	 * 他也会同样的进来
            	 * 那我们看一下这个instance
            	 * 是449
            	 * 另外一个instance是420
            	 * 也就是说在放的时候
            	 * 放了两个对象
            	 * 但是他们的key是同一个
            	 * 也就是说在初始化的时候
            	 * 这个对象是有可能放多个的
            	 * 那具体还要看线程的数量
            	 * 但是一旦放置完成呢
            	 * 我们在取的时候
            	 * 就只会取一个呢
            	 * 因为后执行的
            	 * 把之前执行的
            	 * 覆盖掉
            	 * 因为他们的key一样的
            	 * 那put ok了
            	 * Thread0里面放的是420
            	 * 我们直接F8过来
            	 * 刚刚有说
            	 * Thread0放的是420
            	 * 我们打开这个map
            	 * 我们可以看到map里面放的是420
            	 * 我们再切回Thread1
            	 * 执行put
            	 * 然后F8
            	 * Thread1也到getInstance这里呢
            	 * 我们看一下map里面的值
            	 * 里面已经变成449
            	 * 也就是说后执行的Thread1
            	 * 将要获取449这个Object
            	 * 那我们再切回Thread0
            	 * 看一下这个Map
            	 * 他已经变成449了
            	 * 你看上去这里将要返回同一个对象
            	 * 但是有一点
            	 * 我们现在在干预线程
            	 * 如果不干预线程的话
            	 * 例如Thread0先放置成功
            	 * 他就立刻返回了
            	 * Thread1才去放置
            	 * Thead1返回
            	 * 那这样的话他们返回的对象就不是同一个了
            	 * 我们把断点取消
            	 * 直接F8过
            	 * 从结果看上Thread0和Thread1返回的都是同一个对象
            	 * 但是他们中间还是有隐患的
            	 * 所以这种单例模式适用场景之前也说了
            	 * 根据实际的业务需要
            	 * 我们再看一下这个类
            	 * 我们想一下
            	 * HashMap他本身就不是线程安全的
            	 * 那如果我们把它改成Hashtable的话
            	 * 会不会就变成线程安全呢
            	 * 当然Hashtable是线程安全的
            	 * 我们如果使用Hashtable的话
            	 * 这种模式的单例模式就是线程安全的
            	 * 但是我们频繁去取的时候
            	 * 建议使用同步锁
            	 * 不建议使用Hashtable
            	 * 那我们可以折中一下
            	 * 但是我们使用了静态的HashMap
            	 * 而且直接操作了这个map
            	 * 那在这种场景下呢
            	 * ConCurrentHashMap并不是绝对的线程安全
            	 * 所以我们不考虑安全机制的话
            	 * 这种容器单例模式
            	 * 也是有一定适用场景的
            	 * 在安卓的SDK也用的比较多
            	 * JDK中也有这种模式
            	 * 那后边我们会讲解一些单例模式在源码中的应用
            	 * JDK中使用容器解决单例模式的一个方案
            	 * 和这个比较类似
            	 * 所以在使用这种模式的时候
            	 * 我建议它使用Hashtable
            	 * 所以呢这种单例模式
            	 * 是一个平衡
            	 * 根据业务场景来判断
            	 * 如果我们系统中单例模式非常多的话
            	 * 我们也可以考虑用一个容器把单例
            	 * 他的优点是统一管理
            	 * map相当于一个缓存
            	 * 确认是线程不安全
            	 * 
            	 * 
            	 * 
            	 */
                singletonMap.put(key,instance);
            }
        }
    }

    /**
     * 参数是key
     * 
     * 
     * @param key
     * @return
     */
    public static Object getInstance(String key){
    	/**
    	 * 从singletonMap直接get key就可以了
    	 * 非常简单
    	 * 这种写法非常时候容器在初始化的时候
    	 * 我们就把多个单例对象放入到singletonMap里边
    	 * 统一管理
    	 * 那在使用的时候呢
    	 * 通过key直接从map当中获取单例对象
    	 * 那这里面我们也联想一下
    	 * 假设我们认为singletonMap是一个容器的话
    	 * 对于Spring容器
    	 * Spring容器里讲的单例和我们现在讲的单例
    	 * 是没有关系的
    	 * 但是可以类比一下
    	 * 我们从Spring容器里面取对象的时候
    	 * 默认如果不配置的话
    	 * 是一个单例的
    	 * 那如果我们在类上加上注解
    	 * 或者配置bean的属性为prototype属性的话
    	 * 那他就变成一个多例的
    	 * 但对于这一块我们先不深入讲解
    	 * 那我们先回来接着说这个
    	 * 那我们看一下
    	 * 这里面我们使用HashMap
    	 * 很明显的
    	 * 如果用HashMap肯定不是线程安全的
    	 * 但是如果我们使用他
    	 * 在类初始化的时候
    	 * 去把这个map初始化完成
    	 * 也就是把所有的单例对象都生成完
    	 * 放到这里边
    	 * 用的时候直接用
    	 * 这样是可以的
    	 * 所以这种单例模式
    	 * 还是要看具体的业务场景
    	 * 那我们简单测试一下
    	 * 还是用多线程debug来看一下
    	 * 这种模式单例模式实现方案安全问题
    	 * 首先我们打开Test这个类
    	 * Test类比较多
    	 * 我们看Singleton里面的
    	 * 
    	 * 
    	 * 
    	 */
        return singletonMap.get(key);
    }
}
package com.learn.design.pattern.creational.singleton;

public class T implements Runnable {
    @Override
    public void run() {
//        LazySingleton lazySingleton = LazySingleton.getInstance();
//        System.out.println(Thread.currentThread().getName()+"  "+lazySingleton);
//        LazyDoubleCheckSingleton instance = LazyDoubleCheckSingleton.getInstance();
//        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();;

        ContainerSingleton.putInstance("object",new Object());
        /**
         * 从这里面取一下instance
         * 以为这里调用的是静态方法
         * 所以呢
         * 回到这个类里面
         * 
         * 
         */
        Object instance = ContainerSingleton.getInstance("object");
//        ThreadLocalInstance instance = ThreadLocalInstance.getInstance();

        System.out.println(Thread.currentThread().getName()+"  "+instance);

    }
}
package com.learn.design.pattern.creational.singleton;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//        LazySingleton lazySingleton = LazySingleton.getInstance();

//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());

    	/**
    	 * 现在是两个线程
    	 * 我们把往容器放对象的这个过程
    	 * 放到线程里边
    	 * 让他们并发去放
    	 * 来到T里边
    	 * 
    	 * 
    	 */
        Thread t1 = new Thread(new T());
        Thread t2 = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");

//        HungrySingleton instance = HungrySingleton.getInstance();
//        EnumInstance instance = EnumInstance.getInstance();
//        instance.setData(new Object());

//        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
//        oos.writeObject(instance);

//        File file = new File("singleton_file");
//        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));

//        HungrySingleton newInstance = (HungrySingleton) ois.readObject();
//        EnumInstance newInstance = (EnumInstance) ois.readObject();

//        System.out.println(instance.getData());
//        System.out.println(newInstance.getData());
//        System.out.println(instance.getData() == newInstance.getData());

//        Class objectClass = HungrySingleton.class;
//        Class objectClass = StaticInnerClassSingleton.class;

//        Class objectClass = LazySingleton.class;
//        Class objectClass = EnumInstance.class;
////
//        Constructor constructor = objectClass.getDeclaredConstructor();
//        Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
//
//        constructor.setAccessible(true);
//        EnumInstance instance = (EnumInstance) constructor.newInstance();
//        EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);


//
//        LazySingleton newInstance = (LazySingleton) constructor.newInstance();
//        LazySingleton instance = LazySingleton.getInstance();



//        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
//        StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();

//        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
//        HungrySingleton instance = HungrySingleton.getInstance();

//        System.out.println(instance);
//        System.out.println(newInstance);
        
//        System.out.println(instance == newInstance);

//        EnumInstance instance = EnumInstance.getInstance();
//        instance.printTest();


    }
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值