设计模式之单例模式XXOO

​ 原由

  1. 为了尽可能的节约内存空间,减少GC消耗,并且使应用可以正常运行。
  2. 一个类能否做成单例,最容易区别的地方就在于,这些类,在应用中如果有两个或者两个以上的实例会引起错误, 又或者就是这些类,在整个应用中,同一时刻,有且只能有一种状态。

创建

  1. 静态实例,带有static关键字的属性在每一个类中都是唯一的。
  2. 限制客户端随意创造实例,即私有化构造方法,此为保证单例的最重要的一步。
  3. 给一个公共的获取实例的静态方法,注意,是静态的方法,因为这个方法是在我们未获取到实例的时候就要提供给客户端调用的,所以如果是非静态的话,那就变成一个矛盾体了,因为非静态的方法必须要拥有实例才可以调用。
  4. 判断只有持有的静态实例为null时才调用构造方法创造一个实例,否则就直接返回。

 

单例模式的创建方式:

1.懒汉式

/**
     * 一个静态的实例
     */
    private static Singleton singleton;

    /**
     * 私有化构造函数
     */
    private Singleton(){}

    /**
     * 给出一个公共的静态方法返回一个单一实例
     * @return
     */
    public static Singleton getInstanceOne(){
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

2.饿汉式

    static class Singleton6{

        private static Singleton6 singleton6 = new Singleton6();

        private Singleton6(){}

        public static Singleton6 getInstance(){
            return singleton6;
        }
    }

3.双重枷锁(双重检查模式DCL)

* 假设我们去掉同步块中的是否为null的判断,有这样一种情况,假设A线程和B线程都在同步块外面
* 判断了synchronizedSingleton为null,结果A线程首先获得了线程锁,进入了同步块,然后
* A线程会创造一个实例,此时synchronizedSingleton已经被赋予了实例,A线程退出同步块,
* 直接返回了第一个创造的实例,此时B线程获得线程锁,也进入同步块,此时A线程其实已经创造
* 好了实例,B线程正常情况应该直接返回的,但是因为同步块里没有判断是否为null,直接就是一
* 条创建实例的语句,所以B线程也会创造一个实例返回,此时就造成创造了多个实例的情况。
注意:这种情况在JVM指令重排序就会出问题
    public static Singleton getInstanceThree(){
        if (singleton == null) {
            synchronized(Singleton.class){
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

静态内部类

* 一个类的静态属性只会在第一次加载类时初始化,
* 这是JVM帮我们保证的,所以我们无需担心并发访问的问题。所以在初始化进行一半的时候,别的线程
* 是无法使用的,因为JVM会帮我们强行同步这个过程。另外由于静态变量只初始化一次,所以
* singleton仍然是单例的。
    public static InnerClassSingleton getInstanceFour(){
        return InnerClassSingleton.singleton;
    }

    private static class InnerClassSingleton{
        static InnerClassSingleton singleton = new InnerClassSingleton();

    }

枚举

    /**
     * 单例模式  -- 枚举
     * 枚举类型:表示该类型的对象是有限的几个
     * 我们可以限定为一个,就成了单例
     */
    enum Singleton7 {
        SINGLETON
    }

 

单例模式的并发测试

@Slf4j
public class SingletonTest {

    @Setter
    boolean look;

    static SingletonTest singletonTest;

    /**
     * 单例模式
     *
     * @return
     */
    public static SingletonTest newSingletonTest(){
        if (singletonTest == null) {
            synchronized (SingletonTest.class) {
                if (singletonTest == null) {
                    singletonTest = new SingletonTest();
                }
            }
        }
        return singletonTest;
    }

    /**
     * 静态的内部类 - 线程安全
     *
     * @return
     */
    public static SingletonTest getInstance(){
        return SingletonInstance.instance;
    }

    private static class SingletonInstance{

        static SingletonTest instance = new SingletonTest();

    }

    /**
     * 定义对象集合
     */
    final Set instanceSet = Collections.synchronizedSet(new HashSet<>());

    /**
     *
     * @throws Exception
     */
    public void runSingletonConcurrence() throws Exception{
        this.setLook(true);
        // 使用线程池创建对象
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 100, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(10));
        myTask task;
        for (int i = 0; i < 100 ; i++) {
            task = new myTask(i);
            executor.execute(task);
            log.info("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
                    executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
        }
        log.info(this.look + " before");
        Thread.sleep(1000);
        this.setLook(false);
        Thread.sleep(1000);
        log.info(this.look + " after");
        log.info("单例模式测试-并发测试获取的实例------------------------------");
        instanceSet.forEach(instance -> {
            log.info("获取的实例:{}", instance.toString());
        });
        executor.shutdown();
    }

    class myTask implements Runnable {

        int taskNum;

        public myTask(int taskNum){
            this.taskNum = taskNum;
        }

        @Override
        public void run() {
            while (true){
                log.info("线程{} start-----------{}", taskNum, look);
                if (!look) {
                    Singleton singleton = Singleton.getInstance();
                    instanceSet.add(singleton.toString());
                    log.info("线程{} end-----------", taskNum);
                    break;
                }
            }
        }
    }

    /**
     *
     * @throws Exception
     */
    public void runSingletonConcurrenceTwo() throws Exception{
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        if (!look) {
                            Singleton singleton = Singleton.getInstance();
                            instanceSet.add(singleton.toString());
                            break;
                        }
                    }
                }
            });
        }
        Thread.sleep(5000);
        this.setLook(false);
        Thread.sleep(5000);
        System.out.println("------并发情况下我们取到的实例------");
        for (Object instance : instanceSet) {
            log.info("实例::{}", instance);
        }
        executorService.shutdown();
    }

    public static void main(String[] args) {
        try {
            SingletonTest.getInstance().runSingletonConcurrenceTwo();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用

懒汉式(包含线程安全和线程不安全两种方式)都比较少用;饿汉式和双检锁都可以使用,
可根据具体情况自主选择;在要明确实现 lazy loading 效果时,
可以考虑静态内部类的实现方式;若涉及到反序列化创建对象时,大家也可以尝试使用枚举方式。

 

加油

 

 

 

Guess you like

Origin blog.csdn.net/qq_35731570/article/details/109074042