单例模式,是一种常用的软件设计模式。单例模式用于保证系统中,某一个类只有一个实例。即一个类只有一个对象实例。例如,Hibernate的SessionFactory类,该类是一个重量级的类,里面包含了数据库的所有信息,对于一个数据库应该只有一个SessionFactory实例。
单例模式的实现方式有很多,主要分为懒汉模式和饿汉模式。
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getLazySingleton() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
单例模式就是把构造方法给私有化,使外部无法直接创建对象,而需要我们提供的公开方法来获取,在我们的获取对象方法中,先判断对象是否为空,为空则才会创建一个新对象返回,那么就保证了外部使用的始终是同一个对象。
缺点:线程不安全。
这种写法在极端情况下是非线程安全的,例如,当一个线程判断对象为空,那么开始执行new操作来创建对象,我们知道,在new对象的时候是非常消耗性能的,她并不是一个原子性的操作,那么在并发情况下,另一个线程同时进入方法,在判断对象是否为空的时候,因为上一个线程的new对象还没有完成,此时返回为true,也进入代码块进行对象创建,那么得到的就不是同一个对象了。如果要实现线程安全,我们可以为其加上同步锁来保证线程的同步。
public synchronized static LazySingleton getLazySingleton(){
if(lazySingleton==null){
lazySingleton=new LazySingleton();
}
return lazySingleton;
}
}
加上同步锁能够很好的解决线程安全的问题,可这里又有新的问题了,我们知道对锁的操作是十分消耗性能的,这里每次调用方法都要先获取锁,十分影响效率,因此,我们可以使用双重验证锁的方式来实现单例模式。
public static LazySingleton getLazySingleton(){
if(lazySingleton==null){
synchronized (LazySingleton.class) {
if(lazySingleton==null) {
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
public class EagerSingleton{
private static final EagerSingleton EAGER_SINGLETON = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getEagerSingleton() {
return EAGER_SINGLETON;
}
}
饿汉模式在类加载的时候就初始化了对象,其保证了线程安全性,因为其在类加载的时候就被初始化了,很有可能造成内存垃圾,假如这个实列一直没被调用,那么这个对象是不应该被创建的,对于这种情况我们可以使用懒汉模式。一般情况下,在工作中,饿汉模式用的比较多,其实现简单,只有在类加载的时候稍慢一点,返回对象特别快。
public class InnerClassSingleton{
private InnerClassSingleton() {
}
public static InnerClassSingleton getInnerClassSingleton() {
return InnerClassSingletonHolder.INNER_CLASS_SINGLETON;
}
private static class InnerClassSingletonHolder {
private static final InnerClassSingleton INNER_CLASS_SINGLETON = new InnerClassSingleton();
}
}
这种方式是利用JVM的类加载机制保证了线程安全来实现单例模式,在使用InnerClassSingleton其它方法的时候都不会创建该实例,只有调用getInnerClassSingleton()方法的时候,开始调用静态内部类,此时静态内部类才开始加载,初始化实例。
public enum EnumSingleton {
INSTANCE;
public void execute(){
System.out.println("执行方法");
}
}
枚举类不用多说,和静态类类似,当类加载的时候进行初始化,值得注意的是,枚举类是在jdk1.5才出现的,所以用的传统的饿汉模式的人比较多。
有些时候我们需要把对象进行序列化与反序列化,下面我们就来测试反序列化后的对象与原对象是否是同一个对象。
public class EagerTest {
public static void main(String[] args) throws Exception{
EagerSingleton singleton=EagerSingleton.getEagerSingleton();
FileOutputStream outputStream=new FileOutputStream("eager");
ObjectOutputStream oup=new ObjectOutputStream(outputStream);
oup.writeObject(singleton);
oup.close();
outputStream.close();
FileInputStream inputStream=new FileInputStream("eager");
ObjectInputStream ois=new ObjectInputStream(inputStream);
EagerSingleton eagerSingleton=(EagerSingleton) ois.readObject();
ois.close();
inputStream.close();
System.out.println(singleton==eagerSingleton);
}
}
运行结果:
首先不要忘记类要实现Serializable接口,这里以饿汉模式为例进行测试,可以看到反序列化后两个对象比较为false,说明这种方式创建的单例在反序列化后会出现问题,不能保证实例的唯一性了。解决这个问题只需要在类中加入下面的方法
public class EagerSingleton implements Serializable{
private static final EagerSingleton EAGER_SINGLETON = new EagerSingleton();
private static final long serialVersionUID = -6655710258921072343L;
private EagerSingleton() {
}
public static EagerSingleton getEagerSingleton() {
return EAGER_SINGLETON;
}
private Object readResolve() throws ObjectStreamException {
return getEagerSingleton();
}
}
这里新加入了readResolve()方法,该方法在反序列化的时候会被自动调用,我们再次运行测试代码,看运行结果
再来对枚举类实现的单例模式进行反序列化测试
public class Test {
public static void main(String[] args) throws Exception{
EnumSingleton singleton=EnumSingleton.INSTANCE;
FileOutputStream outputStream=new FileOutputStream("enum");
ObjectOutputStream oup=new ObjectOutputStream(outputStream);
oup.writeObject(singleton);
oup.close();
outputStream.close();
FileInputStream inputStream=new FileInputStream("enum");
ObjectInputStream ois=new ObjectInputStream(inputStream);
EnumSingleton enumSingleton=(EnumSingleton) ois.readObject();
ois.close();
inputStream.close();
System.out.println(singleton==enumSingleton);
}
}
运行结果:
总结:使用枚举类实现的单例模式在反序列化情况下不会发生问题,反序列化生成的对象与原对象一致,其它方式实现的单例模式在反序列化中会出现问题,反序列化生成的对象与原对象不一致,会使单例模式的实例产生多个的问题,解决方法,在类中加上readResove()方法。