单例模式的定义:确保一个类只有一个实例,并提供一个全局的访问点。
在应用中,有些对象我们其实只需要一个,例如线程池之类的对象。如果这类对象产生过多不仅浪费资源还有可能引发其它异常。如果只需要达到只存在一个实例这个要求,我们只需要定义一个全局静态变量,然后约定大家都使用这个变量就可以了。的确也是这样的,但是如果有更好的办法能达到只有一个实例,并且带来更多的便利(如避免一开始就创建这个对象),我们是很乐意学习的。
单例模式共分为三种:懒汉式单例、饿汉式单例、登记式单例。
饿汉式单例
它只有在第一次请求实例时才会去创建。类加载的时候不需要创建,并且第一次创建以后都不需要再创建。
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
这种单例很简单,但有个缺点就是:无论这个类是否被使用都会创建这个实例。
懒汉式单例
这个懒汉式单例,算是对上述饿汉式单例的一个优化。它把对象的创建延迟至使用它的时候。
public class Singleton { private static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
但这种方式,在多线程下是不安全。比如当两个线程同时进入instance == null的判断,那么就会创建两个Singleton对象。要保证这个类的线程安全也很简单,只需要给getInstance方法加上同步锁就行了。
public class Singleton { private static Singleton instance = null; private Singleton() {} public synchronized static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
但通常只有在第一次判断instance == null的时候才会出现不安全性,如果对整个方法加锁,则会产生很多不必要的开销。要避免这种开销,我们只需要在进入instance == null的时候才加锁。
public class Singleton { private static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
登记式单例
上述饿汉式和懒汉式单例都把构造方法都私有了,它是不能被继承的。登记式单例就是为克服这一缺点而设计的。
它会有一个实例池,如果没有会先创建并放到池中再返回实例,如果实例池中存在,则直接从池中取出来。
public class Singleton { private static Map<String, Singleton> instance_pool = new HashMap<String, Singleton>(); static { Singleton instance = new Singleton(); instance_pool.put(Singleton.class.getName(), instance); } protected Singleton() { } public static Singleton getInstance(String name) { if (name == null) name = Singleton.class.getName(); if (instance_pool.get(name) == null) { try { instance_pool.put(name, (Singleton) Class.forName(name).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return instance_pool.get(name); } }