单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。正是由于这个特 点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。
本文将探讨一下在多线程环境下,使用单例对象作配置信息管理时可能会带来的几个同步问题,并针对每个问题给出可选的解决办法。
问题描述
在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化和单例对象的属性更新。
本文描述的方法有如下假设:
1. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。
2. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。
1.1 单例对象的初始化
首先,讨论一下单例对象的初始化同步。单例模式的通常处理方式是,在对象中有一个静态成员变量,其类型就是单例类型本身;如果该变量为null,则创建该单例类型的对象,并将该变量指向这个对象;如果该变量不为null,则直接使用该变量。
其过程如下面代码所示:
- public class GlobalConfig {
- private static GlobalConfig instance = null;
- private Vector properties = null;
- private GlobalConfig() {
- //Load configuration information from DB or file
- //Set values for properties
- }
- public static GlobalConfig getInstance() {
- if (instance == null) {
- instance = new GlobalConfig();
- }
- return instance;
- }
- public Vector getProperties() {
- return properties;
- }
- }
这种处理方式在单线程的模式下可以很好的运行;但是在多线程模式下,可能产生问题。如果第一个线程发现成员变量为null,准备创建对象;这是第二 个线程同时也发现成员变量为null,也会创建新对象。这就会造成在一个JVM中有多个单例类型的实例。如果这个单例类型的成员变量在运行过程中变化,会 造成多个单例类型实例的不一致,产生一些很奇怪的现象。例如,某服务进程通过检查单例对象的某个属性来停止多个线程服务,如果存在多个单例对象的实例,就 会造成部分线程服务停止,部分线程服务不能停止的情况。
1.2 单例对象的属性更新
通常,为了实现配置信息的实时更新,会有一个线程不停检测配置文件或配置数据库的内容,一旦发现变化,就更新到单例对象的属性中。在更新这些信 息的时候,很可能还会有其他线程正在读取这些信息,造成意想不到的后果。还是以通过单例对象属性停止线程服务为例,如果更新属性时读写不同步,可能访问该 属性时这个属性正好为空(null),程序就会抛出异常。
解决方法
2.1 单例对象的初始化同步
对于初始化的同步,可以通过如下代码所采用的方式解决。
- public class GlobalConfig {
- private static GlobalConfig instance = null;
- private Vector properties = null;
- private GlobalConfig() {
- //Load configuration information from DB or file
- //Set values for properties
- }
- private static synchronized void syncInit() {
- if (instance == null) {
- instance = new GlobalConfig();
- }
- }
- public static GlobalConfig getInstance() {
- if (instance == null) {
- syncInit();
- }
- return instance;
- }
- public Vector getProperties() {
- return properties;
- }
- }
这种处理方式虽然引入了同步代码,但是因为这段同步代码只会在最开始的时候执行一次或多次,所以对整个系统的性能不会有影响。
2.2 单例对象的属性更新同步
为了解决第2个问题,有两种方法:
1,参照读者/写者的处理方式
设置一个读计数器,每次读取配置信息前,将计数器加1,读完后将计数器减1.只有在读计数器为0时,才能更新数据,同时要阻塞所有读属性的调用。代码如下。
- public class GlobalConfig {
- private static GlobalConfig instance;
- private Vector properties = null;
- private boolean isUpdating = false;
- private int readCount = 0;
- private GlobalConfig() {
- //Load configuration information from DB or file
- //Set values for properties
- }
- private static synchronized void syncInit() {
- if (instance == null) {
- instance = new GlobalConfig();
- }
- }
- public static GlobalConfig getInstance() {
- if (instance==null) {
- syncInit();
- }
- return instance;
- }
- public synchronized void update(String p_data) {
- syncUpdateIn();
- //Update properties
- }
- private synchronized void syncUpdateIn() {
- while (readCount > 0) {
- try {
- wait();
- } catch (Exception e) {
- }
- }
- }
- private synchronized void syncReadIn() {
- readCount++;
- }
- private synchronized void syncReadOut() {
- readCount--;
- notifyAll();
- }
- public Vector getProperties() {
- syncReadIn();
- //Process data
- syncReadOut();
- return properties;
- }
- }
2,采用"影子实例"的办法
具体说,就是在更新属性时,直接生成另一个单例对象实例,这个新生成的单例对象实例将从数据库或文件中读取最新的配置信息;然后将这些配置信息直接赋值给旧单例对象的属性。如下面代码所示。
- public class GlobalConfig {
- private static GlobalConfig instance = null;
- private Vector properties = null;
- private GlobalConfig() {
- //Load configuration information from DB or file
- //Set values for properties
- }
- private static synchronized void syncInit() {
- if (instance = null) {
- instance = new GlobalConfig();
- }
- }
- public static GlobalConfig getInstance() {
- if (instance = null) {
- syncInit();
- }
- return instance;
- }
- public Vector getProperties() {
- return properties;
- }
- public void updateProperties() {
- //Load updated configuration information by new a GlobalConfig object
- GlobalConfig shadow = new GlobalConfig();
- properties = shadow.getProperties();
- }
- }
注意:在更新方法中,通过生成新的GlobalConfig的实例,从文件或数据库中得到最新配置信息,并存放到properties属性中。
上面两个方法比较起来,第二个方法更好,首先,编程更简单;其次,没有那么多的同步操作,对性能的影响也不大。
相关推荐
这是一个关于多线程下的单例模式优化代码。public class Singleton { private static Singleton instance; private Singleton (){ } public static Singleton getInstance(){ //对获取实例的方法进行同步 if...
Java多线程--解决单例模式中的懒汉式的线程安全问题
该资源是多线程并发下的单例模式-源码,几乎包含了所有方式实现的单例模式,并且能够确保在多线程并发下的线程安全性。 读者可结合本人博客 http://blog.csdn.net/cselmu9?viewmode=list 中的《线程并发之单例模式...
主要演示多线程下的单例模式,分别演示了lock和synchronized两种方案,希望能给大家做一个参考。
入名所示,该文件为最详细的Java单例模式讲解并附有讲解...着重讲解了懒汉模式下怎么实现线程安全。饿汉模式和静态内部类模式如何设置能够避免使用反射方法获取多个实列,以及实现了序列化的类如何避免创建多个实列。
设计模式之七种单例模式代码及ppt,包含多线程环境测试和反序列化测试
Java 单例模式 懒汉模式 //懒汉式 多线程中不可以保证是一个对象
今天小编就为大家分享一篇关于Java多线程实战之单例模式与多线程的实例详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
单例设计模式: 1、构造函数私有 2、定义类型为本身类型的成员 3、类中提供一个公共的并且静态的获取本类实例的方法
主要为大家详细介绍了java多线程之线程安全的单例模式,文章内容全面,感兴趣的小伙伴们可以参考一下
单例模式应该是设计模式中比较简单的一个,也是非常常见的,但是在多线程并发的环境下使用却是不那么简单了,今天给大家分享一个我在开发过程中遇到的单例模式的应用。 首先我们先来看一下单例模式的定义: 一个类有...
在本月的专栏中,David Geary探讨了单例模式以及在面对多线程(multithreading)、类装载器(classloaders)和序列化(serialization)时如何处理这些缺陷。 单例模式适合于一个类只有一个实例的情况,比如窗口管理器...
单例模式是最简单的设计模式之一,但是对于Java的开发者来说...在本月的专栏中,David Geary探讨了单例模式以及在面对多线程(multithreading)、类装载器(classloaders)和序列化(serialization)时如何处理这些缺陷。
计算机后端-Java-Java核心基础-第20章 多线程 11. 线程安全的单例模式之懒汉式.avi
2、掌握 IDEA 环境下的多线程调试方式。 3、掌握保证线程安全的单例模式策略。 4、掌握反射暴力攻击单例解决方案及原理分析。 5、序列化破坏单例的原理及解决方案。 6、掌握常见的单例模式写法。
主要介绍了Java多线程中的单例模式两种实现方式的相关资料,需要的朋友可以参考下
Java设计模式-单例模式详解,包含三种模式式:饿汉,懒汉,登记式,以及线程多同步的处理
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。
单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。一般用于全局接口(比如...同时跟多线程有关,并发中怎么处理多线程去操作这个单利进行实例问题