• 单例模式-1(懒汉式、饿汉式)


    单例模式的应用场景

      单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也十分广泛。

    恶汉单例模式

      恶汉单例模式狮子啊类加载的时候就立即初始化,并且创建单例对象,它绝对的线程安全,在线程还没有出现以前就实例化了,不肯能存才访问安全的问题

      优点:没有任何锁,执行效率比较高,用户体验比懒汉式单例模式更好。

      缺点:类加载的时候就初始化,不管用与不用都占用空间,浪费了内存,可能“占着茅坑不拉屎”。

    Spring 中Ioc 容器ApplicationContentext 本身就是典型的饿汉式单例模式。看下代码。

    public class HungrySingleton {
        private static final HungrySingleton hungrySingletion = new HungrySingleton();
    
        private HungrySingleton(){}
    
        public static HungrySingleton getInstance(){
            return  hungrySingletion;
        }
    }

    还有另一种写法,利用静态代码块机制;

    /**
     * 饿汉式静态块单例
     */
    public class HungryStaticSingleton {
        private static final HungryStaticSingleton hungrySingleton;
        static {
            hungrySingleton = new HungryStaticSingleton();
        }
        private HungryStaticSingleton(){}
    
        public static HungryStaticSingleton getInstance(){
            return hungrySingleton;
        }
    }

    饿汉式单例适用于单例对象较少的情况。

    懒汉式单例模式

      懒汉式单例模式的特点是:被外部调用的时候内部类才会被加载。

    /**
     * 懒汉式单例模式在外部需要使用的时候才进行实例化
     */
    public class LazySimpleSingleton {
        private LazySimpleSingleton(){}
        //静态代码块,公共内存区域
        private static LazySimpleSingleton lazy = null;
        public static LazySimpleSingleton getInstance(){
            if(lazy == null){
                lazy = new LazySimpleSingleton();
            }
            return lazy;
        }
    }

    写一个线程类

    public class ExcetorThread implements Runnable {
        @Override
        public void run() {
            LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
            System.out.println(Thread.currentThread().getName()+":" + singleton);
        }
    }

    测试代码如下

    public class Tests {
        @Test
        void lazySimpleSingetonTest (){
            Thread t1 = new Thread( new ExcetorThread());
            Thread t2 = new Thread( new ExcetorThread());
            t1.start();
            t2.start();
            System.out.println("End");
        }
    }

    运行结果如下

      上面的代码有一定概率出现两种不同结果,这意味着上面的单例存才线程安全隐患。经过调试发现在线程环境下LazySimpleSiingleton被实例化了两次。有时候我们看到的运行结果可能是相同的两个对象,实际上是被后面执行的线程覆盖了,我们看到了一个假象,线程安全隐患依旧存在。

      给getInstance()加上synchronized关键字,是这个方法变成线程同步方法:

    /**
     * 懒汉式单例模式在外部需要使用的时候才进行实例化
     */
    public class LazySimpleSingleton {
        private LazySimpleSingleton(){}
        //静态代码块,公共内存区域
        private static LazySimpleSingleton lazy = null;
        public synchronized static LazySimpleSingleton getInstance(){
            if(lazy == null){
                lazy = new LazySimpleSingleton();
            }
            return lazy;
        }
    }

      synchronized监视锁的运行装填,线程安全的问题解决了。但是synchronized加锁,在线程比较多的情况下,可能导致大批量线程阻塞,从而导致程序性能大幅度下降。那么有没有更好的方法兼顾线程安全和程序性能呢?来看双重监察锁的单例模式:

    public class LazyDoubleCheckSingleton {
        private volatile static LazyDoubleCheckSingleton lazy = null;
        private LazyDoubleCheckSingleton(){}
        public static LazyDoubleCheckSingleton getInstance(){
            if(lazy == null){
                synchronized (LazyDoubleCheckSingleton.class){
                    if(lazy == null){
                        lazy = new LazyDoubleCheckSingleton();
                    }
                }
            }
            return lazy;
        }
    }

      此时当第一个线程调用 getInstance() 方法时,第二个线程也可以调用。当第一个线程执行到 synchronized 时会上锁,第二个线程就会变成 MONITOR 状态,出现阻塞。此时阻塞并不是基于整个 LazySimpleSingleton 类的阻塞,而是在 getInstance() 方法内部阻塞,对于调用者来说只要逻辑不太复杂,是无感知的。

      但是用到 synchronized 总归要上锁,对于性能还是有一定的影响的。

      可以采用内部类的方式:

    public class LazyInnerClassSingleton {
        /**
         * 使用 LazyInnerClassSingleton 的时候,会默认先初始化内部类
         * 如果没有使用,则内部类是不加载的
         */
        private LazyInnerClassSingleton(){}
        public static final LazyInnerClassSingleton getInstance(){
            //再返回结果以前,一定会先加载内部类
            return LazyHolder.LAZY;
        }
        //默认不加载
        public static class LazyHolder{
            private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
        }
    }
    这种形式兼顾了饿汉式单例 的内存浪费问题和 synchronized 的性能问题,完美的屏蔽了这两个缺点。内部类一定是要在方法调用之前初始化,巧妙的避免了线程安全问题。

     反射破坏单例

      上面的单例模式的构造方法除了加上 private 关键字没有任何处理。如果欧文们使用反射调用其构造方法,再调用getInstance() 方法,应该会有两个不用的实例。

    测试代码如下:

      

        @Test
        void LazyInnerClassSingletonTest(){
            try {
                Class<?> clazz = LazyInnerClassSingleton.class;
                Constructor c = null;
                c = clazz.getDeclaredConstructor(null);
                c.setAccessible(true);
                Object o1 = c.newInstance();
                Object o2 = c.newInstance();
                System.out.println(o1 == o2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    运行结果如下

    显然创建了两个不同的实例。现在在其构造方法中做一些限制,一旦出现多次重复创建,则直接抛出异常

    public class LazyInnerClassSingleton {
        /**
         * 使用 LazyInnerClassSingleton 的时候,会默认先初始化内部类
         * 如果没有使用,则内部类是不加载的
         */
        private LazyInnerClassSingleton(){
            if(LazyHolder.LAZY != null){
                throw new RuntimeException("不允许创建多个实例");
            }
        }
        public static final LazyInnerClassSingleton getInstance(){
            //再返回结果以前,一定会先加载内部类
            return LazyHolder.LAZY;
        }
        //默认不加载
        public static class LazyHolder{
            private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
        }
    }

    本文来自博客园,作者:l-coil,转载请注明原文链接:https://www.cnblogs.com/l-coil/p/12863197.html

  • 相关阅读:
    面向对象与面向过程的区别
    IE浏览器上传文件时本地路径变成”C:\fakepath\”的问题
    ldap bdb_db_open 错误解决办法
    转载:技术普及帖:你刚才在淘宝上买了一件东西
    js错误捕捉
    Linux服务器管理系统wdcp Lanmp
    [译]Pro ASP.NET MVC 3 Framework 3rd Edition (Chapter 20 JQuery) 0.引言
    发一个自己写的账号管理软件
    [译]Pro ASP.NET MVC 3 Framework 3rd Edition (Chapter 20 JQuery) 4.Basic jQuery Theory jQuery理论基础
    资源下载(2011609更新)
  • 原文地址:https://www.cnblogs.com/xianquan/p/12863197.html
Copyright © 2020-2023  润新知