单例模式在多线程下的问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式在多线程下的问题相关的知识,希望对你有一定的参考价值。

首先一个简单的单例类:

public class Logger {
    private static Logger log = null;
    // 构造函数私有化
    private Logger() {
    }
    public static Logger getLogger() {
        if (log == null) {
            log = new Logger();
        }
        return log;
    }
}  

该类当放入多线程的环境中,肯定 就会出现问题,如何解决?

   1,第一种方式:在方法getLogger上加上synchronized关键字:

public static synchronized Logger getLogger(){
    if(log == null){
        log = new Logger();
    }
    return log;
}    
    缺点:synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降。
           原因:每次调用getInstance(),都要对对象上锁。
  2,第二种方式:synchronized关键字锁住if方法:
public static Logger getLogger(){
    synchronized (log) {
        if(log == null){
            log = new Logger();
        }
    }
    return log;
}
   该方式的问题:
            在Java指令中创建对象和赋值操作是分开进行的,也就是说log = new Logger();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Logger实例分配空间,然后直接赋值给log成员,然后再去初始化这个Logger实例。这样就可能出错。
            以A、B两个线程为例:
      1,A、B线程同时进入了第一个if判断
      2,A首先进入synchronized块,由于log 为null,所以它执行log = new Logger();
      3,由于JVM内部的优化机制,JVM先画出了一些分配给Logger实例的空白内存,并赋值给log 成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
      4,B进入synchronized块,由于log 此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
      5,此时B线程打算使用Logger实例,却发现它没有被初始化,于是错误发生了。
   解决该问题:
    方案1:创建一个内部类:
public class Logger {
    private static Logger log = null;
    //构造函数私有化
    private Logger(){}
    //创建一个私有的静态内部类
    private static class LoggerFactory{
        private static Logger logger = new Logger();
    }
    public static Logger getLogger(){
        return LoggerFactory.logger;
    }
}    

     方案2:把创建对象和获取对象分开:

public class Logger2 {
    private static Logger2 log = null;
    //构造函数私有化
    private Logger2(){}
    public synchronized void setLogger(){
        if(log == null){
            log = new Logger2();
        }
    }
    public Logger2 getLogger(){
        if(log == null){
            setLogger();//初始化log
        }
        return log;
    }
}    

     方案3:采用“影子实例”同步单例对象属性的同步跟新:

public class Logger {
    private static Logger log = null;
    private Vector properties = null;

    public Vector getProperties() {
        return properties;
    }
    
    public static Logger getLogger(){
        if(log == null){
            syncInit();
        }
        return log;
    }
    
    public static synchronized void syncInit(){//log对象初始化
        if(log == null){
            log = new Logger();
        }
    }

    // 替换掉原有log里面的影子properties
    public void updatePropertis() {
        Logger log1 = new Logger();
        properties = log1.getProperties();
    }

    // 构造函数私有化
    private Logger() {
        //这里模拟从服务器里面读取配置信息,赋值给properties改对象
    }
}
采用类的静态方法,实现单例模式的效果和不使用静态方法实现的单例模式的区别:
    1,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)
    2,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
    3,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。
参考质料:http://www.ibm.com/developerworks/cn/java/l-singleton/

以上是关于单例模式在多线程下的问题的主要内容,如果未能解决你的问题,请参考以下文章

彻头彻尾理解单例模式与多线程

彻头彻尾理解单例模式与多线程

单例模式中的饿汉和懒汉模式

如何保证单例模式在多线程中的线程安全性

蓦然回头-单例模式篇章二

单例模式在多线程中的安全性研究