面向对象设计模式之创建型模式(一):单例模式
李青原(liqingyuan1986@aliyun.com)创作于博客园个人博客(http://www.cnblogs.com/liqingyuan/),转载请标明出处。
单例模式是指一个类只有一个实例化对象,并保证全局访问的都是该对象。
1.单例模式的实现类型:
A.饿汉模式(当类被加载时,即生成单例对象):
1 //code1 2 public class Singleton{ 3 private static singleton = new Singleton(); 4 private Singleton(){} 5 public static Singleton getInstance(){ 6 return singleton; 7 } 8 }
饿汉模式的特点是在static部分创建对象,并且封闭原先的创建对象途径(私有化构造方法),开放唯一的获得对象途径。code1的getInstance方法内容也可以放到构造方法中。
B.懒汉模式(第一次使用对象时生成单例对象):
//code2 public class Singleton{ private volatile static singleton = null; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized(singleton){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
由于懒汉模式在创建方法被调用时才会创建对象,多线程环境下就必然存在安全问题。
我们当然可以在getInstance()方法中加锁来实现,但是这会导致每次创建单例都验证锁,而单例对象恰恰只有一次写的机会,这种方法是对资源的极大浪费。
所以在code2中,使用volatile关键字+双重检查加锁来解决安全问题,双重检查加锁保证了对象只被创建一次,并且只有这次会加锁,而valatile保证了创建的对象反映到其他线程中。(需要注意的是volatile在JDK1.5版本以前的JVM中存在BUG,所以code2不应该在1.5以前的版本中使用)
C.静态内部类懒汉模式:
//code3 public class Singleton{ private static class InnerHandler{ private static Singleton singleton = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return InnerHandler.singleton; } }
无论如何,就算只使用一次,锁都是应当尽量避免的,而code3就能避免这一点——把对象的实例化放到私有静态内部类中,巧妙的使用JVM做到懒汉模式。
仔细研究这种方式,其实对静态内部类来说,对象的创建依然是饿汉式的,但是由于静态内部类被定义成私有,只有当外部类的getInstance方法被调用时,静态内部类才会被JVM加载并实例化外部类对象,所以也等于另辟蹊径,实现了实例化对象的延迟加载。由于JVM对于static语句会做同步处理,所以code3也不存在安全性问题。
D.通过外部容器管理对象实现单例模式:
上面的三种情况,都是通过类内部的代码实现单例模式。在某些情况下,我们希望同一个类的对象,既可以以单例的方式获得,也可以每次获得崭新的对象。这种方式,往往会通过外部容器类来控制。最典型的使用场景就是Servlet,以及Spring IOC中的FactoryBean。无论是JAVA服务器(比如tomcat),还是Spring,都是通过外部容器类管理注册在容器内的对象的生命周期,来模拟单例模式的。
2.单例模式的扩展——对象池模式:
单例模式同一时间只有一个实例存在,而传统多例模式则每次都创建新对象。在实际应用中,可能会存在一种“中间情况”:同一时间可能存在多于一个对象,但是每个对象都可能能被多个调用者使用,而不需要调用者每次都去创建全新的对象。
在这种情况下,可以使用对象池模式。
//code4 public class ObjectPool{ private volatile static Map<String,Object> pool = new HashMap<String,Object>(); private ObjectPool(){} public static Object getObject(String name){ if(pool.get(name) == null){ synchronized(pool){ if(pool.get(cubeName) == null){ pool.put(cubeName,new Object(name)); } } } return pool.get(name); } }
code4是一个最简单的对象池类,正常使用中可能会需要对象的更新 销毁 等功能,可以参考apache的commons-pool包中的对象池设计。
3.单例模式优缺点:
单例模式的优点就是节省资源,无论调用多少次实例化方法,内存中实际存在的对象只有一个。
单例模式的缺点同样明显,如果单例类有成员变量,那么在多线程情况下就存在安全性问题,一个线程的对成员变量的修改可能会在其他线程中产生不希望发生的变化。
针对单例模式的缺点,解决办法有两种:
- 需要进行单例设计的类中,不包含成员变量。没有共享的成员变量,也就不存在多线程的安全问题。
- 对成员变量的操作进行同步。同步的方式也有两种:可以对成员变量加锁,牺牲性能来保证安全性;或者使用ThreadLocal包装成员变量,避免不同线程间的相互影响。后者更高效,但是只适用于线程间不需要数据同步的场景,最典型的例子就是用户登录信息。