确保类只有一个实例,并提供访问它的一个全局点。
第一种(懒汉,线程不安全):
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。
第二种(懒汉,线程安全):public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
第三种(懒汉,线程安全,双重校验锁):
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
属性singleton是被volatile修饰的,因为volatile具有synchronized的可见性特点,也就是说线程能够自动发现volatile变量的最新值。
这样,如果singleton实例化成功,其他线程便能立即发现。
在JDK1.5之后,双重检查锁定才能够正常达到单例效果。
第四种(饿汉):public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
这种方式基于
classloder
机制避免了多线程的同步问题,不过,
instance
在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用
getInstance
方法,
但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化
instance
显然没有达到
lazy loading
的效果。
第五种(饿汉,变种):
Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return this.instance;
}
}
懒汉与饿汉的构造函数都是私有的,彻底的断开了使用构造函数来得到类的实例的通道,但是这样也使得类失去的多态性。
懒汉在多线程中是不安全的,不能保证产生多个实例,而饿汉则不会,因为JVM加载此类时候,对于static属性的初始化只能由一个线程执行且仅一次。懒汉将类对自己的实例化延迟到第一次被应用的时候,而在饿汉中则是在类被加载的时候实例化,这样多次加载会造成多次实例化,但是懒汉由于加了synchronized 关键字,使用了同步处理,在反应速度上要比饿汉慢。
不过在就java语言本身的特点来说,饿汉更符合。
以上两种都失去了多态性,不允许被继承。还有应外一种灵活的实现,将构造函数设为protected,允许被子类继承。
第六种(注册表):import java.util.HashMap;
public class Singleton
{
private static HashMap<String, Singleton> sinRegistry = new HashMap<String, Singleton>();
private static Singleton s = new Singleton();
protected Singleton()
{}
public static Singleton getInstance(String name)
{
if (null == name)
{
name = "Singleton";
}
if (sinRegistry.get(name) == null)
{
try
{
sinRegistry.put(name, (Singleton)Class.forName(name).newInstance());
}
catch (Exception e)
{
e.printStackTrace();
}
}
return sinRegistry.get(name);
}
}
class SingletonChild1 extends Singleton{
public Singleton{}
public static SingletonChild1 getInstance(){
return (SingletonChild1)Singleton.getInstance("SingletonChild1");
}
}
第七种(静态内部类):
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方式同样利用了
classloder
的机制来保证初始化
instance
时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要
Singleton
类被装载了,那么
instance
就会被实例化(没有达到
lazy loading
效果),而这种方式是
Singleton
类被装载了,
instance
不一定被初始化。因为
SingletonHolder
类没有被主动使用,只有显示通过调用
getInstance
方法时,才会显示装载
SingletonHolder
类,从而实例化
instance
。想象一下,如果实例化
instance
很消耗资源,我想让他延迟加载,另外一方面,我不希望在
Singleton
类加载时就实例化,因为我不能确保
Singleton
类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化
instance
显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
对这问题修复的办法是:
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}
如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对这问题修复的办法是: readResolve()直接返回singleton单例,这样我们在内存中时钟保存了唯一的单例对象。
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}