zoukankan      html  css  js  c++  java
  • Java设计模式之单利模式(Singleton)

    单利模式的应用场景:

      单利模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例。并提供一个全局反访问点。单利模式是创建型模式。单利模式在生活中应用也很广泛,比如公司CEO只有一个,部门经理只有一个等。JAVA中ServletCOntext,ServetContextCOnfig等,还有spring中ApplicationContext应用上下文对象,SessionFactory,数据库连接池对象等。使用单利模式可以将其常驻于内存,可以节约更多资源。

    写法:

      1:懒汉模式(线程不安全)

    /**
     * 线程不安全的懒汉式单利模式
     * 
     * Created by gan on 2019/11/17 17:33.
     */
    public class LazySingleton {
        private static LazySingleton instance;
    
        //构造方法私有化
        private LazySingleton() {
        }
    
        public static LazySingleton getInstance() {
            if (instance != null) {
                instance = new LazySingleton();
            }
            return instance;
        }
    }

      上面的代码,提供一个静态对象instance,构造函数私有化防止外部创建对象,提供一个静态的getInstance方法来给访问者一个单利对象。这种写法的缺点就是没有考虑到线程安全问题,当多个访问者同时访问的时候很有可能创建多个对象。之所以叫懒汉式,是因为这种写法是使用的时候才创建,起到了懒加载Lazy loading的作用,实际开发中不建议采用这种写法。

      2:线程安全的懒汉式(加锁)

    /**
     * 线程安全的懒汉式单利模式
     * 
     * Created by gan on 2019/11/17 17:33.
     */
    public class LazySingleton {
        private static LazySingleton instance;
    
        //构造方法私有化
        private LazySingleton() {
        }
    
        public synchronized static LazySingleton getInstance() {
            if (instance != null) {
                instance = new LazySingleton();
            }
            return instance;
        }
    }

      这种写法就是在第一种的基础上添加了synchronized关键字保证了线程安全。这种写法在并发高的时候虽然保证了线程安全,但是效率很低,高并发的时候所有访问的线程都要排队等待,所以实际开发中也不建议采用。

      3:恶汉式(线程安全)

    /**
     * 饿汉式(线程安全)
     * Created by gan on 2019/10/28 22:52.
     */
    public class HungrySigleton {
    
        public static final  HungrySigleton  instance = new HungrySigleton();
    
        private HungrySigleton(){}
    
        public static HungrySigleton getInstance(){
            return instance;
        }
    }

      直接在运行(加载)这个类的时候创建了对象,之后直接访问。显然这种方式没有起到Lazy loading的效果。但是是线程安全的,实际开发中还是比较常用。

      4:静态内部类(线程安全)

    /**
     * 静态内部类方式
     * Created by gan on 2019/11/17 17:46.
     */
    public class StaticInnerClassSingleton {
    
        //构造方法私有化
        private StaticInnerClassSingleton() {}
    
        //内部类
        private static class HolderInnerClass {
            //需要提供单利对象的外部类作为静态属性加载的时候就初始化
            private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
        }
    
        //对外暴漏访问点
        public static StaticInnerClassSingleton getInstance() {
            return HolderInnerClass.instance;
        }
    }

      这种内部类跟饿汉式单利有很多相似的地方,相比饿汉式单利模式的区别也是好处在于:静态内部类不在单利类加载时就加载,而是在调用getInstance()方法的时候才进行加载,达到了类似于懒汉式的效果,而且这种方法又是线程安全的。实际开发中也建议采用。

      5:枚举方法单利(线程安全)

    /**
     * 枚举单利模式
     * Created by gan on 2019/11/17 17:57.
     */
    public enum EnumSingleton {
        INSTANCE;
    
        public void otherMetthod() {
            System.out.println("需要单利对象调用的方法。。。");
        }
    }

      Effective Java作者Josh Bloch提倡的方式,好处有如下:

      1:自由串行化。

      2:保证了一个实例

      3:线程安全

      这种方式防止了单利模式被破坏,而且简洁写法简单,而且绝对的线程安全,但是有个缺点就是不能继承。

      6:双重检查法(通常线程安全,低概率不安全)

    /**
     * Double check
     * Created by gan on 2019/11/17 18:03.
     */
    public class DoubleCheckSingleton {
        private static DoubleCheckSingleton instance;
    
        private DoubleCheckSingleton() {}
    
        public static DoubleCheckSingleton getInstance() {
            if (instance == null) {
                synchronized (DoubleCheckSingleton.class) {
                    if (instance == null) {
                        instance = new DoubleCheckSingleton();
                    }
                }
            }
            return instance;
        }
    }

      上面的这种写法在并发极高的时候也可能会出现问题(当然这种概率非常小,但是毕竟还是有的嘛),解决的方案就是给instance的声明加上volatile关键字即可。于是就出现了下面第7总写法。

      7:Double check(volatile)

    /**
     * Double check volatile
     * Created by gan on 2019/11/17 18:03.
     */
    public class DoubleCheckSingleton {
        private volatile static DoubleCheckSingleton instance;
    
        private DoubleCheckSingleton() {}
    
        public static DoubleCheckSingleton getInstance() {
            if (instance == null) {
                synchronized (DoubleCheckSingleton.class) {
                    if (instance == null) {
                        instance = new DoubleCheckSingleton();
                    }
                }
            }
            return instance;
        }
    }

    volatile关键字的其中一个作用就是禁止指令重排序,把instance声明volatile后,对它的操作就会有一个内存屏障(什么是内存屏障?),这样在赋值完成之前,就不会调用读操作。这里具体的原因网上也是众说纷纭,这里不进行具体阐述。

      8:ThreadLocal实现单利模式(线程安全)

    /**
     * ThreadLocal实现单利模式
     * Created by gan on 2019/11/17 18:17.
     */
    public class ThreadLocalSingleton {
    
        private static final ThreadLocal<ThreadLocalSingleton> threadLocal = new ThreadLocal() {
            @Override
            protected ThreadLocalSingleton initialValue() {
                return new ThreadLocalSingleton();
            }
        };
    
        private ThreadLocalSingleton(){}
    
        public static ThreadLocalSingleton getInstance(){
            return threadLocal.get();
        }
    }

      ThreadLocal会为每个线程提供一个独立的变量副本,从而隔离了多个线程堆数据的访问冲突。对于多线程资源共享问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal则采用了“以空间换时间”的方式(主要就是避免了加锁排队)。 前者提供一份变量,让不同的线程排队访问,而后者为每一个线程提供了一份变量,因此可以同时访问而互不影响。但是实际是创建了多个单利对象的。

    单利模式的破坏:

      1:序列化破坏

        一个对象创建好以后,有时候需要将对象序列化然后写入磁盘。下次在从磁盘中读取并反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即创建型的对象。这样就违背了单利模式的初衷。解决这种方式的方法就是在单利类中新增一个 private Object readResolve();方法即可,具体原因可以看看序列化和反序列化的源码。

      2:反射

        通过反射“暴力破解”也能破坏单利模式,具体暂时不阐述。

      3:克隆

        克隆也会破坏单利模式,具体暂时不阐述。

      代码链接:https://gitee.com/ganganbobo/gps-parent

  • 相关阅读:
    tinyxml优化之一
    vs下C++内存泄露检测
    Cocos2d-x项目移植到WP8系列之九:使用自定义shader
    [leetcode 双周赛 11] 1228 等差数列中缺失的数字
    [leetcode 周赛 158] 1224 最大相等频率
    [leetcode 周赛 158] 1223 掷骰子模拟
    [leetcode 周赛 158] 1222 可以攻击国王的皇后
    [leetcode 周赛 158] 1221 分割平衡字符串
    [leetcode 周赛 157] 1220 统计元音字母序列的数目
    [leetcode 周赛 157] 1219 黄金矿工
  • 原文地址:https://www.cnblogs.com/ganbo/p/11877440.html
Copyright © 2011-2022 走看看