单例设计模式的作用
单例(
Singleton)是一种常见的设计模式。Java中单例模式能保证在JVM中一个类只有一个实例对象(单例对象)。正是由于这个特点,单例对象通常作为程序中放置配置信息的载体,因为它能够保证多个模块读取配置信息是一致的,向外提供的配置信息获取的服务。例如:在某个系统中,该系统的运行参数存储在数据库中或工程的资源文件中,这些参数由单例对象统一读取。系统中的其它对象需要运行参数时,只要访问这个单例即可。
单例模式的两种形式
网上一些介绍单例模式的资料中都会出现懒汉单例模式和饿汉单例模式。下面说一下我的理解。懒汉单例模式:是在第一次使用这个单例对象时去创建这个单例的,后续在使用这个单例时会返回第一次创建的单例对象,所以我们称为懒汉单例模式,也称为懒加载单例模式。饿汉单例模式:在JVM加载类时就初始化这个单例,第一次以及后续使用就会返回JVM加载类时初始化的这个单例。这里需要注意的是类加载到JVM中的时机,第一次创建这个类的实例、调用这个类的静态变量或方法、创建这个类的子类都会导致类被加载到JVM中。例如:一个单例对象在初始化时开销很大影响系统性能,或初始化时间很长导致后续的线程阻塞。而且这个单例对象不确定什么时候被使用到,很有可能在整个系统运行周期内都不会被使用到,这个时候用懒汉单例是很好的选择,用的时候在去初始化。个人比较喜欢在项目中使用懒汉单例,因为饿汉单例我们无法准确的掌握单例对象什么时候初始化(因为导致类加载到JVM的情况有很多)。下面代码给出类两种单例形式的java代码实现:
/**
* 懒汉单例模式
*
* @author zxy
*
*/
public class Singleton {
private static Singleton instance = null;
private Singleton() {
init();
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
private void init() {
System.out.println("相关操作");
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // =>true
SingletonOne sOne1 = SingletonOne.getInstance();
SingletonOne sOne2 = SingletonOne.getInstance();
System.out.println(sOne1 == sOne2);// =>true
}
}
/**
* 饿汉模式
*
* @author zxy
*
*/
class SingletonOne {
private final static SingletonOne instance = new SingletonOne();
private SingletonOne() {
init();
}
public static SingletonOne getInstance() {
return instance;
}
private void init() {
System.out.println("相关操作");
}
}
多线程下的单例对象
上面的懒汉单例模式可以在单线程下很好的运作。但是在多线程下会出现一些问题。问题描述:如果多个线程同时第一次去获取这个单例对象,第一个线程发现instance是null,进入if代码块,执行instance = new Singleton();,还没有执行完,第二个线程发现instance也是null,这时第一个线程执行完instance = new Singleton();并返回instance。第二个线程开始执行instance
= new Singleton();并也返回instance。这样就会造成JVM中存在多个单例对象,程序就会出现错误和一些很奇怪的现象。注意上一个小结中的饿汉模式是线程同步的,原因是利用了classloader机制。下面给出类两种在能在多线程下正常运行的单例的示例代码。
第一种:双重检查锁
/**
* 懒汉单例模式+双重检查锁
* @author zxy
*
*/
public class Singleton {
private static Singleton instance = null;
private Singleton (){
init();
}
private synchronized static Singleton synInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
public static Singleton getInstance() {
if(instance == null){
synInstance();
}
return instance;
}
private void init(){
System.out.println("相关操作");
}
public static void main(String [] args){
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); //=>true
}
}
这里使用了方法同步机制,但是因为synIntance()方法只有第一次初始化时会被执行一次或多次,所以对性能的影响不大。注意:一定不要用synchromized关键字来修饰getInstance(),这样开销和对性能的影响很大。
第二种方式:使用内部类来实现
/**
* 懒汉单例模式+静态内部类
* @author zxy
*
*/
public class Singleton {
static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){
init();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private void init(){
System.out.println("相关操作");
}
public static void main(String [] args){
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); //=>true
}
}
这种方式使用classloader机制来保证INSTANCE被初始化时只有一个线程。Singleton类被加载到JVM中SingletHolder类不一定被加载到JVM中。只有通过显示的调用Singleton.getInstance()时,SingletonHolder才会被加载到JVM中,从而利用classloader机制保证初始化INSTANCE时只有一个线程。
个人比较喜欢使用系统双重检查锁来解决多线下获取单例对象同步的问题。
Spring中的单例Bean
上几节讲单例部分的是为了做项目的时候需要从资源文件读取配置到单例对象中,所有总结一下,加深印象和理解。这几天又有一个新的需求,从数据库的配置表中读取信息到单例对象中(注此表仅有一条数据)。一开始实现的思路是:使用第一种方式:双重检查锁,后来代码都要写完了,发现需要访问数据,需要依赖注入 private SqlSessionTemplate sqlSessionTemplate;,也就说需要将这个单例类交给Spring 容器来管理,是一个Spring容器中的Bean,而类的构造函数是private的,Spring启动时会报错,因为交给Spring容器管理的Bean不能是私有的。后来了解了一下,Spring
Bean本身就是单例对象。但是,到这里并不是万事大吉了,如果多个线程第一次访问这个单例,还是有可能出现初始化多次(多次访问数据库)的情况。其实我这里只是多线程读取这个单例,并没有修改这个单例中的状态,多次访问也不碍什么事。不想让程序有瑕疵,最后还是想了解决方案,其实很解决方法很简单。构造函数的私有化由Spring容器来做了,你只需要用双重检查锁的方式做好线程同步就好了。贴出我的代码:
package com.hsoft.mss.oa.dao.certs;
import net.sf.json.JSONObject;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* root根证书线程同步的单例Bean,读取存储在zsgl_root_zs表中的root根证书信息
* 在第一次使用时读取一次,缓存在configJo中,后续使用将不再重新从表中读取root跟证书信息
* 如果第一次使用且是多线程情况下,使用了双重效验锁机制来保证多线程情况下也仅执行一次getRootZsInfo()
*
* @author
* @date 2015-02-29 09:22
*/
@Component
public class RootCertConfigSingtonImpl implements RootCertConfigSington {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
private JSONObject configJo = null;
@Override
public String getValue(String key){
checkConfig();
return configJo.getString(key);
}
@Override
public JSONObject getConfig(){
checkConfig();
return JSONObject.fromObject(configJo);
}
@Override
public boolean isReadyGood() {
checkConfig();
return !(configJo==null || configJo.isNullObject() || configJo.isEmpty());
}
private void checkConfig(){
if(configJo == null){
synCheckConfig();
}
}
private synchronized void synCheckConfig(){
if(configJo == null){
configJo = getRootZsInfo();
}
}
private JSONObject getRootZsInfo() {
Object obj = sqlSessionTemplate.selectOne("zsgl_root_zs.getRootZsInfo");
return JSONObject.fromObject(obj);
}
}
本文章参照了一下资料:
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
http://conkeyn.iteye.com/blog/1126736
这篇文章将的是多线程环境下的单例Bean,讲的不错,可以看看
http://www.cnblogs.com/doit8791/p/4093808.html