zoukankan      html  css  js  c++  java
  • 单例模式

    为什么需要单例模式

    1. 如果整个程序运行时有一个实例就够用了,何必创建出来多个实例浪费内存呢;
    2. 有些保存数据的实例在程序中只有一份即可,有多份的话可能导致数据不一致问题;

    实现

    1. 非线程安全的懒汉模式

    /**
     * Created by clearbug on 2018/3/10.
     *
     * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思
     */
    public class Singleton {
    
        private static Singleton instance;
        
        // 构造器私有化
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    
    }
    

    优点:

    • 简单易懂;

    缺点:

    • 获取实例的方法 getInstance 线程不安全,多线程环境下可能导致创建了多个实例;

    2. 线程安全的懒汉模式(单锁)

    /**
     * Created by clearbug on 2018/3/10.
     *
     * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思
     */
    public class Singleton {
    
        private static Singleton instance;
    
        // 构造器私有化
        private Singleton() {}
    
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    
    }
    

    这里保证线程安全的方法很简单:就是在获取实例的方法 getInstance 上使用 synchronized 加锁!这样虽然是保证了线程安全,但是每个线程来获取实例是都要经过“获取锁”、“释放锁”两个步骤,无疑效率大打折扣!那么,怎么做到既能保证线程安全又可以尽可能的提高效率呢?这就要说到 DCL(double-checked locking),双重校验锁机制了。

    3. 线程安全的懒汉模式(单锁,双重校验)

    /**
     * Created by clearbug on 2018/3/10.
     *
     * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思
     */
    public class Singleton {
    
        private static volatile Singleton instance;
    
        // 构造器私有化
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    
    }
    

    这种写法相比上两种稍微有点复杂:

    1. 首先,instance 字段要用 volatile 关键字修饰,volatile 关键字的作用就是当某个线程修改了 instance 字段的内容后,其他线程能立即看到 instance 字段的最新值;
    2. 单锁锁定的还是类 Singleton;
    3. 双重校验则是在锁外和锁内均对 instance 是否为 null 做判断,以防止刚开始有多个线程跳过了第一层校验然后又都去初始化 instance 字段的问题;
    4. 在 1.4 及更早版本的 Java 中,JVM 对于 volatile 关键字的实现会导致双重检查加锁的失败;

    4. 线程安全的懒汉模式(静态类内部加载)

    /**
     * Created by clearbug on 2018/3/10.
     *
     * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思
     */
    public class Singleton {
    
        private static class SingletonHolder {
            public SingletonHolder() {
                System.out.println("SingletonHolder constructor method.");
            }
    
            private static Singleton instance = new Singleton();
        }
    
        private Singleton() {
            System.out.println("2. Singleton constructor method.");
        }
    
        public static Singleton getInstance() {
            System.out.println("1. Singleton getInstance method.");
            return SingletonHolder.instance;
        }
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println(Singleton.getInstance());
        }
    
    }
    

    运行结果:

    1. Singleton getInstance method.
    2. Singleton constructor method.
    Singleton@60e53b93
    

    instance 字段被 SingletonHolder 类包装了,并且只是在 Singleton 调用 getInstance 方法时才进行了初始化!并且这种方式也是线程安全的,为啥说它线程安全呢?我的理解是类中的静态字段、静态代码区的代码都是由 JVM 自身调度执行的,所以不存在用户多线程竞争问题!
    以上就是懒汉模式的四种写法了,下面接着说饿汉模式。

    5. 线程安全的饿汉模式(静态字段初始化)

    /**
     * Created by clearbug on 2018/3/10.
     *
     * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思
     */
    public class Singleton {
    
        private static Singleton instance = new Singleton();
    
        private Singleton() {
            System.out.println("2. Singleton constructor method.");
        }
    
        public static Singleton getInstance() {
            System.out.println("1. Singleton getInstance method.");
            return instance;
        }
    
        public static void main(String[] args) {
            System.out.println(Singleton.getInstance());
        }
    
    }
    

    运行结果:

    2. Singleton constructor method.
    1. Singleton getInstance method.
    Singleton@60e53b93
    

    instance 字段在 JVM 加载类 Singleton 时就已经执行了初始化过程,所以是线程安全的!只是如果程序运行期根本没有用到这个实例的话,会造成内存浪费!

    6. 线程安全的饿汉模式(枚举类)

    /**
     * Created by clearbug on 2018/3/10.
     *
     * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思
     */
    public enum  Singleton {
    
        INSTANCE;
    
        public void otherMethod() {
            System.out.println("otherMethod");
        }
    
        public static void main(String[] args) {
            Singleton.INSTANCE.otherMethod();
        }
    
    }
    

    这种方法我之前没咋听说过,看下面参考博客里面 cielosun 这位老铁说:“Effective Java作者Josh Bloch 提倡的方式,在我看来简直是来自神的写法”,听起来很牛逼的样子!
    这种方法也是线程安全的,我的理解是枚举类的字段也是 JVM 在加载该类时初始化的,所以也不存在用户多线程竞争问题!而且,由于枚举类的特性,就算是反序列化之后运行的 JVM 中也只有一个实例。
    以上就是饿汉模式的两种写法,下面说说单例模式的一些注意点吧:

    1. 序列化和反序列化问题对单例的影响,可以通过修改 readResolve 方法来解决;
    2. 多个类加载器加载对单例的影响?尴尬,忽然想起来我对类加载器的知识也不大熟练就先不说了吧!
    3. 通过反射调用单例类的私有构造方法对单例的影响,可以通过修改构造方法,以便第二次创建实例时抛出异常!

    参考

    https://www.cnblogs.com/cielosun/p/6582333.html
    http://www.blogjava.net/kenzhh/archive/2016/03/28/357824.html
    http://www.importnew.com/18872.html

  • 相关阅读:
    poj 3040 Allowance
    poj 2393 Yogurt factory
    【BZOJ1833】数字计数(ZJOI2010)-数位DP
    【BZOJ4820】硬币游戏(SDOI2017)-概率+高斯消元+KMP
    【BZOJ3626】LCA(LNOI2014)-树链剖分+离线处理
    【BZOJ4817】树点涂色(SDOI2017)-LCT+LCA+线段树
    【BZOJ1135】LYZ(POI2009)-线段树+Hall定理
    【CF392D】Three Arrays-set+multiset
    【51Nod1688】LYKMUL-线段树+乘法原理
    【BZOJ2956】模积和-数论分块
  • 原文地址:https://www.cnblogs.com/optor/p/8542359.html
Copyright © 2011-2022 走看看