单例模式时比较常见的设计模式,它所解决的问题是:如何保证某个类的实例在全局只有唯一一个。
尽管实现起来比较简单,但要注意潜在的同步性问题。
单例模式特点
- 构造方法是私有的
- 要保证程序每次运行时只会创建一次这个类的实例
- 需要提供可以公开访问这个实例的方法
单例模式的典型例子
public class Singleton {
static Singleton mInstance;
private Singleton() {
System.out.println("Singleton constructor");
}
public static Singleton getInstance() {
if (mInstance == null) {
mInstance = new Singleton();
}
return mInstance;
}
}
问题
上面的代码暗藏了致命问题:在多线程处理中有可能会产生同步性的问题,导致实例化2次。
下面验证以下,先创建一个Thread类,run方法中获取Singleton实例并打印:
class MyThread extends Thread {
int mId;
public MyThread(int id) {
mId = id;
}
@Override
public void run() {
System.out.println("Thread-" + mId + " running");
System.out.println(mId + " getInstacne:" + Singleton.getInstance());
}
}
执行代码如下:
public static void main(String[] args) {
MyThread t1 = new MyThread(1);
MyThread t2 = new MyThread(2);
MyThread t3 = new MyThread(3);
t1.start();
t2.start();
t3.start();
}
重复运行几次,确实是有几率出现调用2次构造方法的问题,这无疑打破了原有的设计。
Thread-1 running
Thread-2 running
Singleton constructor
1 getInstacne:com.wils.pattern.singleton.Singleton@7dca373f
Singleton constructor
2 getInstacne:com.wils.pattern.singleton.Singleton@65c16078
Thread-3 running
3 getInstacne:com.wils.pattern.singleton.Singleton@65c16078
解决办法
有3种解决方案,各有优缺点,适用于不同情景。
1.将getInstance改成同步方法。
缺点:如果需要频繁调用,那么效率会很低。
优点:简单有效地解决问题。如果不考虑效率可以考虑这种方式。
public static synchronized Singleton getInstance() {
if (mInstance == null) {
mInstance = new Singleton();
}
return mInstance;
}
2.将实例化提前到成员便利定义的时候。
缺点:造成创建和运行时方面的负担加重。
优点:简单有效,如果初始化负担不重可以使用。
public class Singleton {
private static Singleton mInstance = new Singleton();
private Singleton() {
System.out.println("Singleton constructor");
}
public static Singleton getInstance() {
return mInstance;
}
}
3.双重检查
缺点:不适用于java1.4及更早的版本。
优点:性能较好。
public class Singleton {
//使用volatile
static volatile Singleton mInstance;
private Singleton() {
System.out.println("Singleton constructor");
}
public static Singleton getInstance() {
if (mInstance == null) {
//加同步锁
synchronized(Singleton.class){
//再次检查
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}