单例模式
懒汉式
顾名思义,lazy loading(延迟加载),在需要的时候创建单例对象,而不是随着软件系统的运行或者当类被加载器加载的时候就创建。下面是最简单的懒汉式单例模式:
class Singleton {
public static Singleton singleton = null; // 私有的、类型为Singleton自身的静态成员变量
// 构造方法被设为,防止外部使用new来创建对象,破坏单例
private Singleton() {
System.out.println("构造函数被调用");
}
// 公有的静态方法,供外部调用来获取单例模式
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
在单线程环境下,多次调用getInstance()方法均为同一个对象。然后在多线程中不得不考虑线程安全的问题
现有多线程测试代码如下:
public class SingletonTest {
public static void main(String[] args) {
for (int i = 0; i <50; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Singleton.getInstance();
}
}).start();;
}
}
}
代码中使用匿名类部类的方法实现了Runable接口,实现多线程,创建50个线程,其中一次的运行结果为:
构造函数被调用
构造函数被调用
构造函数被调用
构造函数被调用
构造函数被调用
构造函数被调用
显然,Singleton的构造方法不止一次被调用,着违背了单例模式的初衷。这说明,懒汉式在多线程中是线程不安全的。如果在getInstance方法上同步锁,但是锁住整个方法的粒度过大,效率不高。看如下代码
class Singleton{
private static Singleton singleton = null;
private Singleton() {
System.out.println("构造函数被调用");
}
public static Singleton getInstance() {
if(singleton == null) {
synchronized(Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
上述代码中,getInstance方法中,在判断空之后上锁,这样做看似解决了线程安全问题,其实不然。设现有线程A和B,在t1时刻线程A和B均已通过判空语句但都未取得锁资源;t2时刻时,A先取得锁资源进入临界区(被锁的代码块),执行new操作创建实例对象,然后退出临界区,释放锁资源。t3时刻,B取得被A释放的锁资源进入临界区,执行new操作创建实例对象,然后退出临界区,释放锁资源。明显地,Singleton被实例化两次。所以,如代码段1-3这样写也不能保证线程安全。
双重校验锁(Double checked locking)
双重校验锁分别在代码锁前后进行判断空校验,避免了多个有机会进入临界区的线程都创建对象,同时避免了后来线程在先来线程创建对象后仍未退出临界区的情况下等待。代码如下:
class Singleton{
private volatile static Singleton singleton = null; //此处加了volatile关键字
private Singleton() {
System.out.println("构造函数被调用");
}
public static Singleton getInstance() {
if(singleton == null) {
synchronized(Singleton.class) {
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
经多次试验说明,双重校验锁式是线程安全的
网友评论