• 单例模式及其并发问题


    单例模式是设计模式中使用比较广泛的一种设计模式,这个模式的目的是在系统中只实现一个类的实例。

    首先给出一个Singleton的简单实现:

    public class Singleton {
        private static Singleton singleton = null;
        private Singleton() {  }
        public static Singleton getInstance() {
            if (singleton== null) {
                singleton= new Singleton();
            }
            return singleton;
        }
    }
    

    1.构造函数的私有,表明了该类无法被外部实例化。

    2.如果要获取该类的实例,只能通过该类的静态方法getInstance方法。

    目前这个程序有个问题就是如果该类未初始化的时候同时有多个线程访问getInstance方法,它们的判断都为null,这个时候就会创建多个给 类的实例,这是不允许的。这种情况下就应该实现线程互斥。即加上synchronized来实现线程的互斥。 将上述代码更改为

    public class Singleton
    {
        private static Singleton singleton = null;
        private Singleton() {  }
        public static Singleton getInstance() {
            if (singleton== null) {
                synchronized (Singleton.class) {
                    singleton= new Singleton();
                }
            }
            return singleton;
        }
    }
    

    上述代码中将会同时只有一个类进行实例化,但是如果同时进入多个程序判断为null,之后就会排队进行,但是已经判断结束了,所以仍旧会创建多个实例,只是不同时而已。

    public class Singleton
    {
        private static Singleton singleton = null;
        private Singleton()  {    }
        public static Singleton getInstance() {
            if (singleton== null)  {
                synchronized (Singleton.class) {
                    if (singleton== null)  {
                        singleton= new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
    

    向上述代码中进行两次的判断非空,上述代码看起来没有问题了,但是仍存在一个小问题。
    主要在于singleton = new Singleton()这句,这句并非是一个原子操作,事实上在JVM中这句话大概做了下面三件事情。
    1.给Singleton分配空间。
    2.调用Singleton的构造函数来初始化成员变量,形成实例。
    3.将Singleton对象指向分配的内存空间(执行完这句Singleton才是非null了)
    但是在JVM的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是1-2-3也可能是 1-3-2.如果是后者,则3执行完毕、2未执行之前,线程被抢占了,这时已经是非null了(但是初始化并未完成),所以线程二会不进行初始化而直接返 回,这回就会产生错误。
    对此我们需要把Singleton声明成volatile就可以了。

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

    使用volatile关键字有两个作用:
    1)这个变量不会在多个线程中存在副本,而是直接从内存读取。
    2)这个关键字会禁止指令重排序的优化。

    public class Singleton
    {
        private volatile static Singleton singleton = new Singleton();
        private Singleton()  {    }
        public static Singleton getInstance()   {
            return singleton;
        }
    }
    

    像上述代码中,singleton在第一次加载内存的时候就已经初始化了,后面的过程中没有初始化过程,也就不会产生Singleton实例,因此保证了线程的安全。但是上述代码即使未调用getInstance方法 也会产生一个Singleton实例。

    public class Singleton {
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
        private Singleton (){}
        public static final Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    上述代码中采用了jvm本身的机制来保证了线程的安全,SingletonHolder是私有的,除了getInstance方法不能调用,这保证了只有getInstance方法调用的时候才进行初始化。而且在读的时候不存在问题。

  • 相关阅读:
    用才情绽放的幸福之花
    我的爱车,你在哪里
    爱在网络,有没有错
    假如能抱着美女写诗
    只想爱你
    创业者和爱因斯坦的10大共同点(不是不可比的)
    心的感谢
    成大事必备9种能力.9种手段.9种心态
    一颗新星在陨落
    C++/C学习笔记(九)
  • 原文地址:https://www.cnblogs.com/benniaoxuefei/p/5493173.html
Copyright © 2020-2023  润新知