1、单例对象单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。正是由于这个特 点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。本文将探讨一下在多线程环境下,使用单例对象作配置信息管理时可能会带来的几个同步问
2、题,并针对每个问题给出可选的解决办法。问题描述 在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化和单例对象的属性更新。本文描述的方法有如下假设:1. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。2. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。1.1 单例对象的初始化 首先,讨论一下单例对象的初始化同步。单例模式的通常处理方式是,在对象中有一个静态成员变量,其类型就是单例类型本身;如果该变量为null,则创建该单例类型的对象,并将该变量
3、指向这个对象;如果该变量不为null,则直接使用该变量。其过程如下面代码所示:Java代码 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 getInsta
4、nce() 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中有多个单例类型的实例。如果这个单例类型的成员变量在运行过程中变化,会 造成多个单例类
5、型实例的不一致,产生一些很奇怪的现象。例如,某服务进程通过检查单例对象的某个属性来停止多个线程服务,如果存在多个单例对象的实例,就 会造成部分线程服务停止,部分线程服务不能停止的情况。1.2 单例对象的属性更新 通常,为了实现配置信息的实时更新,会有一个线程不停检测配置文件或配置数据库的内容,一旦发现变化,就更新到单例对象的属性中。在更新这些信 息的时候,很可能还会有其他线程正在读取这些信息,造成意想不到的后果。还是以通过单例对象属性停止线程服务为例,如果更新属性时读写不同步,可能访问该 属性时这个属性正好为空(null),程序就会抛出异常。解决方法 2.1 单例对象的初始化同步对于初始化的同
6、步,可以通过如下代码所采用的方式解决。Java代码 18 public class GlobalConfig 19 private static GlobalConfig instance = null; 20 private Vector properties = null; 21 private GlobalConfig() 22 /Load configuration information from DB or file 23 /Set values for properties 24 25 private static synchronized void syncInit() 26
7、if (instance = null) 27 instance = new GlobalConfig(); 28 29 30 public static GlobalConfig getInstance() 31 if (instance = null) 32 syncInit(); 33 34 return instance; 35 36 public Vector getProperties() 37 return properties; 38 39 这种处理方式虽然引入了同步代码,但是因为这段同步代码只会在最开始的时候执行一次或多次,所以对整个系统的性能不会有影响。 2.2 单例对象的
8、属性更新同步 为了解决第2个问题,有两种方法:1,参照读者/写者的处理方式 设置一个读计数器,每次读取配置信息前,将计数器加1,读完后将计数器减1.只有在读计数器为0时,才能更新数据,同时要阻塞所有读属性的调用。代码如下。Java代码 40 public class GlobalConfig 41 private static GlobalConfig instance; 42 private Vector properties = null; 43 private boolean isUpdating = false; 44 private int readCount = 0; 45 pri
9、vate GlobalConfig() 46 /Load configuration information from DB or file 47 /Set values for properties 48 49 private static synchronized void syncInit() 50 if (instance = null) 51 instance = new GlobalConfig(); 52 53 54 public static GlobalConfig getInstance() 55 if (instance=null) 56 syncInit(); 57 5
10、8 return instance; 59 60 public synchronized void update(String p_data) 61 syncUpdateIn(); 62 /Update properties 63 64 private synchronized void syncUpdateIn() 65 while (readCount 0) 66 try 67 wait(); 68 catch (Exception e) 69 70 71 72 private synchronized void syncReadIn() 73 readCount+; 74 75 priv
11、ate synchronized void syncReadOut() 76 readCount-; 77 notifyAll(); 78 79 public Vector getProperties() 80 syncReadIn(); 81 /Process data 82 syncReadOut(); 83 return properties; 84 85 2,采用影子实例的办法具体说,就是在更新属性时,直接生成另一个单例对象实例,这个新生成的单例对象实例将从数据库或文件中读取最新的配置信息;然后将这些配置信息直接赋值给旧单例对象的属性。如下面代码所示。Java代码 86 public
12、class GlobalConfig 87 private static GlobalConfig instance = null; 88 private Vector properties = null; 89 private GlobalConfig() 90 /Load configuration information from DB or file 91 /Set values for properties 92 93 private static synchronized void syncInit() 94 if (instance = null) 95 instance = n
13、ew GlobalConfig(); 96 97 98 public static GlobalConfig getInstance() 99 if (instance = null) 100 syncInit(); 101 102 return instance; 103 104 public Vector getProperties() 105 return properties; 106 107 public void updateProperties() 108 /Load updated configuration information by new a GlobalConfig
14、object 109 GlobalConfig shadow = new GlobalConfig(); 110 properties = shadow.getProperties(); 111 112 注意:在更新方法中,通过生成新的GlobalConfig的实例,从文件或数据库中得到最新配置信息,并存放到properties属性中。上面两个方法比较起来,第二个方法更好,首先,编程更简单;其次,没有那么多的同步操作,对性能的影响也不大。多线程中单例模式Abstract 在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制,也就是说只有当使用到这个实例的时候才会创建
15、这个实例,这个好处在单例模式中得到了广泛应用。这个机制在single-threaded环境下的实现非常简单,然而在multi-threaded环境下却存在隐患。本文重点介绍惰性加载机制以及其在多线程环境下的使用方法。(作者numberzero,参考IBM文章Double-checked locking and the Singleton pattern,欢迎转载与讨论)1 单例模式的惰性加载通常当我们设计一个单例类的时候,会在类的内部构造这个类(通过构造函数,或者在定义处直接创建),并对外提供一个static getInstance方法提供获取该单例对象的途径。例如:Java代码 public
16、 class Singleton private static Singleton instance = new Singleton(); private Singleton() public static Singleton getInstance() return instance; public class Singleton private static Singleton instance = new Singleton(); private Singleton() public static Singleton getInstance() return instance; 这样的代
17、码缺点是:第一次加载类的时候会连带着创建Singleton实例,这样的结果与我们所期望的不同,因为创建实例的时候可能并不是我们需要这个实例的时候。同时如果这个Singleton实例的创建非常消耗系统资源,而应用始终都没有使用Singleton实例,那么创建Singleton消耗的系统资源就被白白浪费了。 为了避免这种情况,我们通常使用惰性加载的机制,也就是在使用的时候才去创建。以上代码的惰性加载代码如下:Java代码 public class Singleton private static Singleton instance = null ; private Singleton() pub
18、lic static Singleton getInstance() if (instance = null ) instance = new Singleton(); return instance; public class Singleton private static Singleton instance = null; private Singleton() public static Singleton getInstance() if (instance = null) instance = new Singleton(); return instance; 这样,当我们第一次
19、调用Singleton.getInstance()的时候,这个单例才被创建,而以后再次调用的时候仅仅返回这个单例就可以了。2 惰性加载在多线程中的问题先将惰性加载的代码提取出来: Java代码 public static Singleton getInstance() if (instance = null ) instance = new Singleton(); return instance; public static Singleton getInstance() if (instance = null) instance = new Singleton(); return inst
20、ance; 这是如果两个线程A和B同时执行了该方法,然后以如下方式执行:1. A进入if判断,此时foo为null,因此进入if内2. B进入if判断,此时A还没有创建foo,因此foo也为null,因此B也进入if内3. A创建了一个Foo并返回4. B也创建了一个Foo并返回此时问题出现了,我们的单例被创建了两次,而这并不是我们所期望的。3 各种解决方案及其存在的问题3.1 使用Class锁机制以上问题最直观的解决办法就是给getInstance方法加上一个synchronize前缀,这样每次只允许一个现成调用getInstance方法:Java代码 public static synchronized Singleton getInstance() if (instance = null ) instance = new Singleton(); return instance; public static synchronized Singlet
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1