Java 多线程下的单例模式
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。正是由于这个特 点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。
本文将探讨一下在多线程环境下,使用单例对象作配置信息管理时可能会带来的几个同步问题,并针对每个问题给出可选的解决办法。
问题描述
在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化和单例对象的属性更新。
本文描述的方法有如下假设:
. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。
. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。
1.1 单例对象的初始化
首先,讨论一下单例对象的初始化同步。单例模式的通常处理方式是,在对象中有一个静态成员变量,其类型就是单例类型本身;如果该变量为null,则创建该单例类型的对象,并将该变量指向这个对象;如果该变量不为null,则直接使用该变量。
其过程如下面代码所示:
Java代码 收藏代码
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 单例对象的初始化同步
对于初始化的同步,可以通过如下代码所采用的方式解决。
Java代码 收藏代码
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.只有在读计数器为0时,才能更新数据,同时要阻塞所有读属性的调用。代码如下。
Java代码 收藏代码
public class GlobalConfig {
private static GlobalConfig instance;
private Vector properties = null;
private boolean isUpdating = false;
private int readCount = ;
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 > ) {
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;
}
}
,采用"影子实例"的办法
具体说,就是在更新属性时,直接生成另一个单例对象实例,这个新生成的单例对象实例将从数据库或文件中读取最新的配置信息;然后将这些配置信息直接赋值给旧单例对象的属性。如下面代码所示。
Java代码 收藏代码
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属性中。
上面两个方法比较起来,第二个方法更好,首先,编程更简单;其次,没有那么多的同步操作,对性能的影响也不大。
Java 多线程下的单例模式的更多相关文章
- java集群优化——多线程下的单例模式
在最初学习设计模式时,我为绝佳的设计思想激动不已,在以后的project中.多次融合设计模式,而在当下的设计中.我们已经觉察出了当初设计模式的高瞻远瞩.可是也有一些不足,须要我们去改进.有人说过.世界 ...
- Java多线程核心技术(五)单例模式与多线程
本文只需要考虑一件事:如何使单例模式遇到多线程是安全的.正确的 1.立即加载 / "饿汉模式" 什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 ...
- JAVA多线程下高并发的处理经验
java中的线程:java中,每个线程都有一个调用栈存放在线程栈之中,一个java应用总是从main()函数开始运行,被称为主线程.一旦创建一个新的线程,就会产生一个线程栈.线程总体分为:用户线程和守 ...
- Java多线程编程之单例模式
延迟加载:“懒汉模式” 延迟加载是指在调用getInstance()方法时创建实例.常见的方法是在getInstance()方法中实例化new.实现代码如下: 但是因为getInstance()中有多 ...
- java/ kotlin下的单例模式
单例模式属于创建型模式, 顾名思义,就是说整个系统中只有一个该对象的实例. 为什么要使用单例模式? 1, 对于一些需要频繁创建,销毁的对象, 使用单例模式可以节省系统资源 2, 对于全局持有的对象,单 ...
- Java多线程编程核心技术---单例模式与多线程
立即加载/饿汉模式 立即加载就是使用类的时候已经将对象创建完毕. public class MyObject { //立即加载方式==饿汉模式 private static MyObject myOb ...
- C++多线程下的单例模式
一.懒汉模式:即第一次调用该类实例的时候才产生一个新的该类实例,并在以后仅返回此实例. 需要用锁,来保证其线程安全性:原因:多个线程可能进入判断是否已经存在实例的if语句,从而non thread s ...
- Java多线程中的单例模式
一.在多线程环境下创建单例 方式一: package com.ietree.multithread.sync; public class Singletion { private static cla ...
- [JAVA]多线程下如何确定执行顺序性
最近在讨论一个下载任务:要求文件下载后进行打包,再提供给用户下载: 如何确保打包的线程在所有下载文件的线程执行完成后进行呢? 看看下面三个兄弟的本事: CountDownLatch.CyclicBar ...
随机推荐
- 升级AutoMapper后遇到的“Missing map”与“Missing type map configuration”问题
前几天发现 AutoMapper 3.3 的一个性能问题(详见:遭遇AutoMapper性能问题:映射200条数据比100条慢了近千倍),于是将 AutoMapper 升级至最新的 5.1.1 看是否 ...
- 两种include方式及filter中的dispatcher解析
两种include方式 我自己写了一个original.jsp,另外有一个includedPage.jsp,我想在original.jsp中把includedPage.jsp引进来有两种方式: 1.& ...
- 记一次Url重写_发布之后iis 404
把api封装完,客户要求app的url能不能不变(客户之前用的php的api开发app,已经开发了很多了,所以希望不改动url).但是这个规则要求是:xx/api.php?s=/{controller ...
- 在.net中使用aquiles访问Cassandra(二)
上文中我们已经建立了项目的基本结构,今天实现数据的修改.在NoSQL中,通常添加和修改都认为是对数据的一种Mutation. 1.建立描述修改Row的实体. public class RowMut ...
- EF架构~为分组添加位运算聚合方法
回到目录 我们知道在Linq里的分组groupby可以对集合中一个或者多个字段进行分组,并对其中一个属性进行聚合,而Linq为我们提供了多种聚合方法,由aver,sum,count等,而在大叔权限体系 ...
- PHP面向对象06_异常处理
oop06异常处理 2014-9-2 8:36:33 NotePad++ By jiancaigege 摘要: 1.异常处理 2.类中常用函数 异常处理 语法格式: try{ //捕获异常 }catc ...
- atitit 短信验证码的源码实现 .docx
atitit 短信验证码的源码实现 .docx 参考 Atitit usrQBM1603短信验证码规范1 主要方法1 源码实现1 参考 Atitit usrQBM1603短信验证码规范 主要方法 L ...
- angularjs with node service
- Chrome清除dns缓存
Chrome清除dns缓存 为了加快访问速度,Google Chrome浏览器采用了预提DNS记录,在本地建立DNS缓存的方法,加快网站的连接速度.你在谷歌Chrome浏览器的地址栏中输入about: ...
- leancloud 手机注册用户(调用API) 教程
// 从storybord 连线过来的button方法(注册按钮) - (IBAction)regist:(UIButton *)sender { AFHTTPSessionManager *mana ...