Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 268|回复: 0

[设计模式学习]单例对象同步问题探讨

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-11-6 00:02:35 | 显示全部楼层 |阅读模式
    单例对象(Singleton)是一种常用的设计模式。在java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。正是由于这个特点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。
                                                                                        
          例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。  本文将探讨一下在多线程环境下,使用单例对象作配置信息管理时可能会带来的几个同步问题,并针对每个问题给出可选的解决办法。
      
      
        问题描述  在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化和单例对象的属性更新。  本文描述的方法有如下假设:  1. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。  2. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。  1.1 单例对象的初始化  首先,讨论一下单例对象的初始化同步。单例模式的通常处理方式是,在对象中有一个静态成员变量,其类型就是单例类型本身;如果该变量为null,则创建该单例类型的对象,并将该变量指向这个对象;如果该变量不为null,则直接使用该变量。  其过程如下面代码所示:  
       
         
          
         
    1. public class GlobalConfig {
    2.     private static GlobalConfig instance = null;
    3.     private Vector properties = null;
    4.     private GlobalConfig() {
    5.       //Load configuration information from DB or file
    6.       //Set values for properties
    7.     }
    8.     public static GlobalConfig getInstance() {
    9.       if (instance == null) {
    10.         instance = new GlobalConfig();
    11.       }
    12.       return instance;
    13.     }
    14.     public Vector getProperties() {
    15.       return properties;
    16.     }
    17.   }
    复制代码

          
         
         
          这种处理方式在单线程的模式下可以很好的运行;但是在多线程模式下,可能产生问题。如果第一个线程发现成员变量为null,准备创建对象;这是第二个线程同时也发现成员变量为null,也会创建新对象。这就会造成在一个JVM中有多个单例类型的实例。如果这个单例类型的成员变量在运行过程中变化,会造成多个单例类型实例的不一致,产生一些很奇怪的现象。例如,某服务进程通过检查单例对象的某个属性来停止多个线程服务,如果存在多个单例对象的实例,就会造成部分线程服务停止,部分线程服务不能停止的情况。  1.2 单例对象的属性更新  通常,为了实现配置信息的实时更新,会有一个线程不停检测配置文件或配置数据库的内容,一旦发现变化,就更新到单例对象的属性中。在更新这些信息的时候,很可能还会有其他线程正在读取这些信息,造成意想不到的后果。还是以通过单例对象属性停止线程服务为例,如果更新属性时读写不同步,可能访问该属性时这个属性正好为空(null),程序就会抛出异常。  解决方法  2.1 单例对象的初始化同步  对于初始化的同步,可以通过如下代码所采用的方式解决。  
       
         
          
         
    1.   public class GlobalConfig {
    2.     private static GlobalConfig instance = null;
    3.     private Vector properties = null;
    4.     private GlobalConfig() {
    5.       //Load configuration information from DB or file
    6.       //Set values for properties
    7.     }
    8.     private static synchronized void syncInit() {
    9.       if (instance == null) {
    10.         instance = new GlobalConfig();
    11.       }
    12.     }
    13.     public static GlobalConfig getInstance() {
    14.       if (instance == null) {
    15.         syncInit();
    16.       }
    17.       return instance;
    18.     }
    19.     public Vector getProperties() {
    20.       return properties;
    21.     }
    22.   }
    23.                      
    复制代码

          
         
          这种处理方式虽然引入了同步代码,但是因为这段同步代码只会在最开始的时候执行一次或多次,所以对整个系统的性能不会有影响。 2.2 单例对象的属性更新同步  为了解决第2个问题,有两种方法:  1,参照读者/写者的处理方式  设置一个读计数器,每次读取配置信息前,将计数器加1,读完后将计数器减1.只有在读计数器为0时,才能更新数据,同时要阻塞所有读属性的调用。代码如下。  
       
         
          
         
    1.   public class GlobalConfig {
    2. private static GlobalConfig instance;
    3. private Vector properties = null;
    4. private boolean isUpdating = false;
    5. private int readCount = 0;
    6. private GlobalConfig() {
    7.    //Load configuration information from DB or file
    8.       //Set values for properties
    9. }
    10. private static synchronized void syncInit() {
    11.   if (instance == null) {
    12.    instance = new GlobalConfig();
    13.   }
    14. }
    15. public static GlobalConfig getInstance() {
    16.   if (instance==null) {
    17.    syncInit();
    18.   }
    19.   return instance;
    20. }
    21. public synchronized void update(String p_data) {
    22.   syncUpdateIn();
    23.   //Update properties
    24. }
    25. private synchronized void syncUpdateIn() {
    26.   while (readCount > 0) {
    27.    try {
    28.     wait();
    29.    } catch (Exception e) {
    30.    }
    31.   }
    32. }
    33. private synchronized void syncReadIn() {
    34.   readCount++;
    35. }
    36. private synchronized void syncReadOut() {
    37.   readCount--;
    38.   notifyAll();
    39. }
    40. public Vector getProperties() {
    41.   syncReadIn();
    42.   //Process data
    43.   syncReadOut();
    44.   return properties;
    45. }
    46.   }
    复制代码

          
         
           2,采用"影子实例"的办法  具体说,就是在更新属性时,直接生成另一个单例对象实例,这个新生成的单例对象实例将从数据库或文件中读取最新的配置信息;然后将这些配置信息直接赋值给旧单例对象的属性。如下面代码所示。  
       
         
          
         
    1.   public class GlobalConfig {
    2.     private static GlobalConfig instance = null;
    3.     private Vector properties = null;
    4.     private GlobalConfig() {
    5.       //Load configuration information from DB or file
    6.       //Set values for properties
    7.     }
    8.     private static synchronized void syncInit() {
    9.       if (instance = null) {
    10.         instance = new GlobalConfig();
    11.       }
    12.     }
    13.     public static GlobalConfig getInstance() {
    14.       if (instance = null) {
    15.         syncInit();
    16.       }
    17.       return instance;
    18.     }
    19.     public Vector getProperties() {
    20.       return properties;
    21.     }
    22.     public void updateProperties() {
    23.       //Load updated configuration information by new a GlobalConfig object
    24.       GlobalConfig shadow = new GlobalConfig();
    25.       properties = shadow.getProperties();
    26.     }
    27.   }
    复制代码

          
         
          注意:在更新方法中,通过生成新的GlobalConfig的实例,从文件或数据库中得到最新配置信息,并存放到properties属性中。  上面两个方法比较起来,第二个方法更好,首先,编程更简单;其次,没有那么多的同步操作,对性能的影响也不大。
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-25 11:32 , Processed in 0.410162 second(s), 48 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表