NotOnlyJava

http://www.ibm.com/developerworks/cn/java/j-lo-serial/
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

singleton容器

Posted on 2013-07-05 14:27  NotOnlyJava  阅读(271)  评论(0编辑  收藏  举报

Singleton容器

我们的代码中大量充斥着单例的实现,单例有他的优点,也有他的缺点,
优点:
1 对象只用创建一次,既能减少创建对象的开销也能减少GC的开销,尤其是各种需要解析XML的view中,使用单例能得到性能提升。
2 单例里的数据方便存取
缺点:
1 单例不能被回收,如果里面有List或map,系统注销时一定要clear
2 单例的属性存取需要同步,才能保证多线程下安全访问。
在我们的系统中充斥着大量的单例实现,如下:

1 基于饿汉模式的(类加载时就创建对象):

public class CounterManager {
private static CounterManager INSTANCE = new CounterManager();
....
private CounterManager() {
...    
}
public static CounterManager getInstance() {
return INSTANCE;
}
}

 


2 基于DCL的懒汉模式的(第一次调用时创建对象):

public class SusimTransferView extends AbstractContentView{
...
private static volatile SusimTransferView INSTANCE = null;

private SusimTransferView()
{
}

public static SusimTransferView getInstance()
{
if(INSTANCE == null)
{
synchronized (SusimTransferView.class)
{
if(INSTANCE == null)
{
INSTANCE = new SusimTransferView();
}
}
}
return INSTANCE;
}

 

当前实现单例不只这两种方式,但是不管以哪种方式,只要是有类自身来显现单例的,都需要私有构造函数,编写
大量雷同的代码,实际上如果把单例的实现委托给一个专门的类管理,既能消除重复的代码,也方便对所有的单例
的生命周期进行管理,只要我们访问这些对象都从委托类中获取,我们就不需要被委托类必须编写私有的构造函数,
工厂模式正符合委托类的特征,通过工厂类,我可以方便的生产我们需要的对象,在单例和非单例直接切换也很容
易,并且能方便的管理所有对象的生命周期,这点在spring得到经典的实现,但是我们的系统中,系统注销时,只
需要清除某些对象,这点不方便用spring来实现,我参考spring写了一个BeanFactory类,这个类能方便的创建
单例对象,且自动管理单例对象的生命周期,系统注销或退出时,自动销毁所有的单例,唯一的要求就是需要被管
理的bean符合javabean规范,至少有一个无参构造函数,代码如下

/**
* 实例工厂主要是提供单例对象
* 自动管理对象的生命周期
* 
* @author CZP
* 
*/
public class BeanFactory implements SystemEventListener{

private ConcurrentHashMap<String, Object> cache;

private static final BeanFactory INSTANCE = new BeanFactory();

private BeanFactory() {
cache = new ConcurrentHashMap<String, Object>();
}

/**
* 单例,可以重复注册到系统中
* @return
*/
public static BeanFactory getInstance()
{
return INSTANCE;
}

/**
* 获取单例是线程安全的
* 
* @param cls
* @return
*/
public Object getSingletonObject(Class<?> cls)
{
try {
cache.putIfAbsent(cls.getSimpleName(), cls.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return cache.get(cls.getSimpleName());
}

/**
* 系统注销或系统退出时清空所有的bean
* 
*/
@Override
public void onSystemStatusChange(SystemEvent event)throws SscInterruptedException {
if (event.getEventType().equalsIgnoreCase(
SystemEventConstants.EVENT_LOGOUT)
|| event.getEventType().equalsIgnoreCase(SystemEventConstants.EVENT_SHUTDOWN)) {
cache.clear();
AppParser.getSystemStatusManager().removeSystemEventListener(this);
}
}

}

 


要获取单例非常简单,进行如下调用即可:

VPNManagementView view = (VPNManagementView)BeanFactory.getInstance().getSingletonObject(VPNManagementView.class);

 


之后BeanFactory 会自动管理这个bean的生命周期,注销或退出时,自动销毁该bean,实现
bean创建bean销毁与bean使用的解耦,调用者只需要拿到这个bean,进行相应的业务调用即可。
这个getSingletonObject没有加锁,能实现高效的调用,但是有人会怀疑这个是不是线程安全的
我们进行以下测试,起5个线程同时调用一个类:

public static void main(String[] args) {
ExecutorService s = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
s.execute(new Runnable() {

@Override
public void run() {
VPNManagementView view = (VPNManagementView)BeanFactory.getInstance().getSingletonObject(VPNManagementView.class);
System.out.println("["+Thread.currentThread().getName()+"]"+ view);

}
});
}
s.shutdownNow();

}

 


输出如下:

<nowiki>

[pool-1-thread-4]com.shjv.tdscdma.omc.client.opm.terminal.vpn.view.VPNManagementView@47a0d4
[pool-1-thread-3]com.shjv.tdscdma.omc.client.opm.terminal.vpn.view.VPNManagementView@47a0d4
[pool-1-thread-2]com.shjv.tdscdma.omc.client.opm.terminal.vpn.view.VPNManagementView@47a0d4
[pool-1-thread-5]com.shjv.tdscdma.omc.client.opm.terminal.vpn.view.VPNManagementView@47a0d4
[pool-1-thread-1]com.shjv.tdscdma.omc.client.opm.terminal.vpn.view.VPNManagementView@47a0d4

</nowiki>

可以看出,5个线程取到的都是同一个对象,说明这个方法是线程安全的,这个得益于ConcurrentHashMap的实现。