前段时间在做接口测试的时候,发现开发的代码中会用到很多Java设计模式,比如工厂模式、单列模式以及观察者模式等等。在测试的过程我往往会先大体看一下开发写的代码,有时在code review的过程就能发现代码中存在一些bug,在这里主要将我近期发现的一个单列模式的bug以及对单列模式做的一些调研和大家分享一下。
单列模式主要是保证在Java应用程序中,一个class只有一个实例对象存在。
在很多操作中,比如建立目录,数据库连接都需要这样的单线程操作。Singleton模式就为我们提供了这样实现的可能。使用singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收。
一般Singleton模式通常有几种形式:
1. public class Singleton{
private Singleton(){}// 注意构筑函数必须是private的
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
2.private static Singleton instance = null;
public static synchronized Singleton getInstance() {
//这个方法比上面有所改进,不用每次都进行生成对象,只是第一次
//使用时生成实例,提高了效率!
if (instance==null)
instance=new Singleton();
return instance;
}
上面第二种形式是Lazy initialization,也就是说第一次调用时使用初始的Singleton,以后就不用再生成了。
注意到第二种形式中synchronized,这个synchronized非常重要,如果没有synchronized,那么使用getInstance()是有可能得到多个实例的。我们在这里主要也就是讨论第二种形式,因为我的bug也就是在第二种形式的单列模式中找到的。
首先我们来看以下这段最普通的单列模式代码:
list1:
public class Singleton
{
private static Singleton instance;
private Vector v;
private boolean inUse;
private Singleton()
{
v = new Vector();
v.addElement(new Object());
inUse = true;
}
public static Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
}
我们看到上面这段代码,在getInstance()方法前面没有synchronized,那么在多线程的情况下我们来看看程序的运行:
1. Thread1 调用getInstance()方法的时候发现instance是null在line1处.
2. Thread1 开始进入if模块,但是在执行line2的时候Thread2抢占了Thread1的cpu执行时间。
3. Thread2 开始调用getInstance()方法并且发现instance是null,因此Thread2进入if模块并创建了一个新的Singleton实例。
4. 在line3,Thread2将创建的instance返回。
5. Thread2的cpu执行权限被Thread1抢占,这个时候Thread1开始执行line2并又创建了一个Singleton实例,并返回了该实例。
这个时候大家就会发现getInstance()方法被调用了两次,并创建了两个实例,bug就因此产生了,单列模式创建了两个实例。
那么这个时候我们的developer会给getInstance()前面加上synchronized。如list2的代码:
public static synchronized Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
这个时候我们发现一起都ok了,list2的代码在多线程调用的情况下已经可以很好的工作了,但是我们会发现一个问题,在多线程调用的情况下,我们只需要在第一次调用getInstance()的时候使用synchronized,因为只有在第一次调用的时候才会产生一个Singleton的实例,后续的调用其实都不需要synchronized,因为第一次调用getInstance()后,后续的线程都发现instance!=null并返回instance。因为我们加了sychronized,那么在多线程的情况,每一次调用getInstance()方法的时候都会做sychronization,这样势必会影响程序的执行效率,设想如果是一个web项目的话,那么就会出现web性能瓶颈,那么我们应该如何去提高这段代码的性能呢,大家可以做思考。