本文介绍23种设计模式之单例模式。
定义
确保一个类只有一个实例,并提供一个全局的访问点。单例模式最常见和常用,像:线程池,数据库连接池,缓存,HttpServlet 等。
描述
- 模式名称:SINGLETON(单件)
- 类型:对象创建型模式
- 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 适用性:
- 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
- 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
- 效果:
- 优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用。
- 缺点:
- 单例模式一般没有接口,扩展困难。
单例模式的几种实现方式
饿汉式
public class Singleton {
// 饿汉式 使用static保证线程安全
private static final Singleton instance = new Singleton();
private static Singleton(){} // 私有化构造,只能内部创建
public Singleton getInstance(){
return instance;
}
}
优点:没有加锁,执行效率会提高。缺点:类加载时就初始化,浪费内存。饿汉式是一种很常用的单例模式,代码简单,通过类加载模式保证线程安全,就是会产生不必要的内存占用。
懒汉式
- 线程不安全,最基本的实现方式,当获取实例时才进行创建。
public class Singleton2 {
private Singleton2() {
}
private Singleton2 instance;
public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
- 通过 sychronized 锁住代码块保证线程安全
public class Singleton3 {
private Singleton3() {
}
private static Singleton3 instance;
public static synchronized Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
- 双重锁校验,通过锁细化提高执行效率,使用volatile来禁止指令重排序,在volatile的作用一篇中有具体介绍,这里就不多延伸了。
public class Singleton4 {
private Singleton4() {
}
private volatile static Singleton4 instance;
public static Singleton4 getInstance() {
if (instance == null) {
synchronized(Singleton4.class){
if(instance == null){
instance = new Singleton4();
}
}
}
return instance;
}
}
枚举
枚举类是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束。
枚举类能够实现接口,但不能继承类,枚举类使用enum定义后在编译时就默认继承了java.lang.Enum类,而不是普通的继承Object类.枚举类会默认实现Serializable和Comparable两个接口,且采用enum声明后,该类会被编译器加上final声明,故该类是无法继承的。 枚举类的内部定义的枚举值就是该类的实例.除此之外,枚举类和普通类一致.因此可以利用枚举类来实现一个单例模式。 通过枚举防止 clone,序列化,反射 破坏单例。以双重锁校验位例,实现clonable,Serializable。通过比对hashcode是否相同,来验证是否是同一个对象。public static void main(String[] args) throws Exception {
//通过getInstance()获取
Singleton4 singleton = Singleton4.getInstance();
System.out.println("singleton的hashCode:"+singleton.hashCode());
//通过反射获取
Constructor<Singleton4> constructor = Singleton4.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton4 reflex = constructor.newInstance();
System.out.println("反射的hashCode:"+reflex.hashCode());
//通过克隆获取
Singleton4 clob = (Singleton4) Singleton4.getInstance().clone();
System.out.println("克隆的hashCode:"+clob.hashCode());
//通过序列化,反序列化获取
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Singleton4.getInstance());
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton4 serialize = (Singleton4) ois.readObject();
System.out.println("序列化的hashCode:"+serialize.hashCode());
}
运行结果如下:
singleton的hashCode:1490180672 反射的hashCode:460332449 克隆的hashCode:1919892312 序列化的hashCode:985655350 可以看出确实存在单例被破坏的问题。枚举类防止clone的原因要看下Enum类:
/**
* 抛出 CloneNotSupportedException。 这保证了枚举
* 永远不会被克隆,这是保留它们的“单例”所必需的
*
*
* @return (never returns)
*/
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
保证不能反射的原因在Constructor中:
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// 如果是枚举类的话会抛出异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
枚举实例代码:
public enum Singleton5 {
INSTANCE;
}
public static void main(String[] args) throws Exception {
//通过getInstance()获取
Singleton5 singleton = Singleton5.INSTANCE;
System.out.println("singleton的hashCode:"+singleton.hashCode());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Singleton5.INSTANCE);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton5 serialize = (Singleton5) ois.readObject();
System.out.println("序列化的hashCode:"+serialize.hashCode());
}
结果如下:
singleton的hashCode:1490180672 序列化的hashCode:1490180672