zoukankan      html  css  js  c++  java
  • 单例模式在多线程下的问题

    首先一个简单的单例类:

    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/
  • 相关阅读:
    SpringCloudAlibaba学习笔记-简介
    SpringCloudAlibaba学习笔记-目录
    go语言学习笔记-目录
    go语言学习笔记-配置idea开发go编程语言并配置导入本地包
    go语言学习笔记-Windows10开发环境安装和环境变量配置
    我与阿里云的日常-QuickBI开发教程
    我与阿里云的日常-阿里云帐号注册
    消息队列 RabbitMq(6)高级特性
    消息队列 (5) RabbtMQ SpringBoot整合
    Nginx的安装和使用
  • 原文地址:https://www.cnblogs.com/my-haohao/p/5613006.html
Copyright © 2011-2022 走看看