进击的小羊

  1. 首页
  2. 后端
  3. Java
  4. 正文

单例模式-容器注册模式

2019年9月2日 503点热度 0人点赞 0条评论

容器的注册式单例

除了上文中提到的单例实现方式以外,还有一种通过容器注册的方式完成单例的实现。
代码样例如下:

public class ContainerSingleton {

    private ContainerSingleton() {
    }

    //ConcurrentHashMap 保证容器put线程安全
    private static final Map<String, Object> container = new ConcurrentHashMap<>();

    public static Object getBean(String className) {
        //double check 保证单例模式
        synchronized (container) {
            if (!container.containsKey(className)) {

                Object o = null;
                try {
                    o = Class.forName(className).newInstance();
                    container.put(className, o);
                    return o;
                } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }

        }
        return container.get(className);
    }

}

//测试代码
public class User {
    private String userId;
    private String userName;
}

public class TestContainerSingleton {
    public static void main(String[] args) {

        Thread thread1 = new Thread(() -> {
            User user1 = (User) ContainerSingleton.getBean("com.zixiu.singleton.User");
            System.out.println(Thread.currentThread().getName() + ":" + user1);

        });
        Thread thread2 = new Thread(() -> {
            User user2 = (User) ContainerSingleton.getBean("com.zixiu.singleton.User");
            System.out.println(Thread.currentThread().getName() + ":" + user2);
        });

        thread1.start();
        thread2.start();

        System.out.println("Execute thread.");
    }

}

这种方式比较巧妙的是通过Map的特性保证对象名称的唯一性,通过类似于double check的方式确保单例的安全。

线程内的单例模式

ThreadLocal:ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。

上文说到了容器注册式的单例实现,那么针对TheadLocal关键字来说同样可以实现单例模式。

public class ThreadLocalSingleton {
    //私有化构造器
    private ThreadLocalSingleton() {
    }

    //单例实现
    private static final ThreadLocal<ThreadLocalSingleton> instance = ThreadLocal.withInitial(ThreadLocalSingleton::new);

    //访问点
    public static Object getInstance() {
        return instance.get();
    }
}

//测试代码
public class TestThreadLocalSingleton {
    public static void main(String[] args) {

        System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
        System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
        System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
        System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
        System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
        System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());

        Thread thread1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());

        });
        Thread thread2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalSingleton.getInstance());
        });

        thread1.start();
        thread2.start();

        System.out.println("Execute thread.");
    }
}

测试结果如下:

截屏2021-09-03 00.22.43

可以看出针对主线程来说,创建的对象是不变的,同样在Thread0和Thread1中,在线程内的创建的对象也是不变的。

通过ThreadLocal的源码可以解释这种情况的原因,在ThreadLocal的实现中,是一个key为线程对象,value为自定义的对象的Map结构。

    ...
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    ...

ThreadLocal会通过set方法将声明的对象以及当前的线程信息存入到ThreadLocalMap。ThreadLocal这种实现方式也是容器注册的一种方式,不过容器是每个线程互不干扰的,因此创建的单例也是每个线程保证单例的。

使用场景:例如可以通过数据源的名称,完成多数据源动态的切换,完成数据源的路由。

单例模式的优缺点

优点

  1. 使用简单
  2. 减少内存的占用,仅提供一个对象,减少对资源的占用
  3. 设置全局的访问点,保证安全性

缺点

  1. 单例模式从某种程度上来说是不符合Java的开闭原则的,需要修改代码才能拓展单例模式的内容
  2. 没有对应的接口,拓展性不强
标签: 设计模式
最后更新:2022年3月12日

qizi

一生懸命,步履不停

点赞
< 上一篇
下一篇 >
分类
  • Java
  • 大数据
  • 数据库
  • 杂项
文章目录
  • 容器的注册式单例
  • 线程内的单例模式
  • 单例模式的优缺点
    • 优点
    • 缺点

COPYRIGHT © 2022 进击的小羊. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang