【转自:www.chenyudong.com/archives/java-singleton.html】作者:东东东 陈煜东
?
在设计模式中,单例模式(Singleton)是最长见得一种设计模式之一。什么是单例模式呢?就是在整个系统中,只有一个唯一存在的实例。这样的情况可以干什么用呢?比如可以统计网站的访问量,一些连接池(数据库连接池等)。
那么怎么能保证只有一个对象的存在呢?首先得有一个static的实例,这个方法保证了一个class只有一个实例。还得防止外界使用构造器来new一个实例。
1 2 3 4 5monospace !important; margin: 0px !important; padding: 0px !important; vertical-align: baseline !important; line-height: 1.1em !important; color: #008200 !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; background-image: none !important; float: none !important; height: auto !important; overflow: visible !important; width: auto !important; direction: ltr !important; display: inline !important;">//一个没有封装的单例模式
public
class
Singleton {
????
public
static
final
Singleton singleton =?
new
Singleton();
????
private
Singleton(){}
}
外界就可以使用Singleton.singleton
这样的方法来调用了。但是这样存在的问题就是分装不够好,添加一个方法,返回singleton的引用。如下
//最简单的封装单例改进版。饿汉模式
public
class
Singleton {
????
public
static
final
Singleton singleton =?
new
Singleton();
????
private
Singleton(){}
????
public
static
Singleton getInstance(){?
return
singleton; }
}
当代码写到这,我们终于可以松一口气了,原来单例模式也很简单呀,就这么几行的代码。对,其实这个是最简单的一种方式,能够应付大部分的场景的。
不过,其他class在引用Singleton而不使用的时候,虚拟机会自动加载这个类,并且实例化这个对象(这点知道Java虚拟机的类加载就会了解那么一些)。于是我们就有了下面的写法。
经常能看见其他的单例模式会教下面的代码,这样的人估计是从《设计模式 – 可复用面向对象软件的基础》那本书看来的。这本书使用的是C++语言写的,然后就将其转到了Java平台来。
首先他们会说我们的代码应该先这样,在调用的时候,发现为null,再进行实例化该类。
01 02 03 04 05 06 07 08 09 10public
class
Singleton {
????
public
static
final
Singleton singleton =?
null
;
????
private
Singleton(){}
????
public
static
Singleton getInstance(){
????????
if
(singleton ==?
null
){?
//如果singleton为空,表明未实例化
???????????
singleton =?
new
Singleton();
????????
}
????????
return
singleton;
????
}
}
然后他们还会说,这样在多线程
的情况下会出现这样的情况:两个进程都进入到if (singleton == null),于是两个线程都对这个进行实例化,这样就出问题啦。
所以他们又说应该使用synchronize关键字,在实例化之前,进行加锁行为。于是又产生了一下的代码
01 02 03 04 05 06 07 08 09 10 11 12 13public
class
Singleton {
????
public
static
final
Singleton singleton =?
null
;
????
private
Singleton(){}
????
public
static
Singleton getInstance(){
????????
if
(singleton ==?
null
){?
//如果singleton为空,表明未实例化
???????????
synchronize (Singleton.
class
){
???????????????
if
( singleton ==?
null
) {?
// double check 进来判断后再实例化。
???????????????????
singleton =?
new
Singleton();
???????????????
}
????????
}
????????
return
singleton;
????
}
}
他们就会说,他们的这个代码使用了double check (双重检测)
,以防止这样的情况:当两个线程执行完第一个singleton == null 后等待锁, 其中一个线程获得锁并进入synchronize后,实例化了,然后退出释放锁,另外一个线程获得锁,进入又想实例化,会判断是否进行实例化了,如果存在,就不进行实例化了。
他们会说,这样的代码perfect,很完美,既解决了多线程带来的问题,又解决了延迟实例化的方式。
我觉得这样的代码只是将C++版的单例模式复制到Java平台,没有Java的特色。第一个就是一个Java特色的代码,它解决了多线程的问题,因为JLS(Java Language Specification)中规定了一个类(Singleton.class)只会被初始化一次,但是不能解决延迟实例化的情况。如需要延迟实例化,可以看下面的方法,使用内部类来实现。
刚才说了,第一个不能解决延迟实例化Singleton对象的问题。所以我们使用内部类来进行,看看代码。
01 02 03 04 05 06 07 08 09 10 11 12 13 14//一个延迟实例化的内部类的单例模式
public
final
class
Singleton {
?
????
//一个内部类的容器,调用getInstance时,JVM加载这个类
????
private
static
final
class
SingletonHolder {
????????
static
final
Singleton singleton =??
new
Singleton();
????
}
?
????
private
Singleton() {}
?
????
public
static
Singleton getInstance() {
????????
return
SingletonHolder.singleton;
????
}
?
}
我们来看看这个代码。首先,其他类在引用这个Singleton的类时,只是新建了一个引用,并没有开辟一个的堆空间存放(对象所在的内存空间)。接着,当使用Singleton.getInstance()
方法后,Java虚拟机(JVM)会加载SingletonHolder.class
(JLS规定每个class对象只能被初始化一次),并实例化一个Singleton对象。
这样做就可以解决前面说的对线程多次实例化对象
和延迟实例化对象
的问题了。
缺点:不过你会使用这样复杂的方式嘛?代码那么多。只是为了延迟一个对象的实例化,引入另外一个class。就为了延迟那么一次对象延迟的实例化,延缓Java的heap堆内存。为此付出的代价是引入一个class,需要在Java的另外一个内存空间(Java PermGen 永久代内存,这块内存是虚拟机加载class文件存放的位置)占用一个大块的空间。
好了,在许多情况其实用第一种方法就差不多可以了。在特殊情况下,还是存在着一些问题。
如果使用Java的反射机制来生成对象的话,那么单例模式就会被破坏。
1 2 3 4 5//使用反射破坏单例模式
Class c = Class.forName(Singleton.
class
.getName());
Constructor constructor = c.getDeclaredConstructor();
constructor.setAccessible(
true
);
Singleton singleton = (Singleton)ct.newInstance();
对于用反射破坏单例模式的,是不对其进行代码保护的,即由此造成的后果,由写反射的构建单例实例的人负责。所以我们就不用担心反射带来的问题了。
对于分布式上的单例模式,应该使用RMI(Remote Method Invocation 远程方法调用)来进行。或者使用web serivce,在单个服务器上存在单例,其他的机器使用SOAP协议进行访问。
在不同的ClassLoader加载Singleton,他们是不一样的。就像在不同的package中相同的类名,他们是不同的类。同样的,在不同的ClassLoader上加载的类,他们尽管代码一样,还是属于不同的类。
这样,需要自己写classloader,保证Singleton.class的加载唯一。
参考文章:http://www.oschina.net/question/9709_102019
对于这个问题,在还没实例化单例的时候,对象不存在,单实例化后,那么singleton的引用就存在的,只要Singleton.class存在虚拟机中。那么什么时候Singleton.class会被回收呢?对于这个问题,牵扯了许多的问题。因为Singleton对象存在,所以Singleton.class就也存在,这样形成了相互依赖,所以不会被JVM垃圾回收。
网上有个文章验证了他的想法 http://blog.csdn.net/zhengzhb/article/details/7331354
如果你的Singleton序列化了,那么通过反序列化方式可以生成一个对象。通过增加readResolve方法来解决。如下
1 2 3 4 5 6 7//最简单的封装单例改进版。饿汉模式。序列化及反序列化解决
public
class
Singleton?
implements
java.io.Serializable {
????
public
static
final
Singleton singleton =?
new
Singleton();
????
private
Singleton(){}
????
public
static
Singleton getInstance(){?
return
singleton; }
????
private
Object readResolve() {??
return
singleton;?? }
}
Object的clone()
方法默认是抛出CloneNotSupportedException
异常的,所以只要不覆盖该方法,调用的时候,也会抛出异常。
effective java作者提到用enum枚举来实现单例模式。
扩展阅读:使用enum来进行Java的单例模式:http://coolxing.iteye.com/blog/1446648