常用设计模式之单例模式,以及单例模式的线程安全问题

常用设计模式之单例模式,以及单例模式的线程安全问题

介绍

有时候允许自由创建某个类的对象没有意义,还可能会造成系统性能下降。如果让一个类只能创建一个实例,这种设计模式被称为单例设计模式。其基本思路就是:定义一个静态变量来缓存该类的实例;私有化构造器;然后对外暴露一个静态方法来获取该类的实例。单例设计模式根据初始化对象的时机不同又分为饿汉式和懒汉式。

饿汉式

饿汉式非常简单,直接贴代码:

public class Singleton {
	
	private static Singleton instance = new Singleton();
	
	private Singleton() {
		
	}
	
	public static Singleton getInstance() {
		return instance;
	}

}

懒汉式

懒汉式是在调用getInstance方法时进行判断,如果对象已经实例化,则直接返回,如果没有实例化,先实例化一个对象,再返回。

public class Singleton {

	private static Singleton instance;
	
	private Singleton() {
		
	}
	
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}

}

对多线程比较敏感的程序员可能已经意识到了,这样的方式是存在线程安全问题的,并且初始化对象所需的时间越久,越有可能出现线程安全问题,你如果不信,我们可以做一个实验,在构造器里sleep两毫秒来模拟延迟,然后对getInstance方法做并发测试。

	private Singleton() {
		try {
			Thread.sleep(2);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Thread th = new Thread(() -> {
			Singleton instance = Singleton.getInstance();
			System.out.println(instance);
		});
		Thread th1 = new Thread(() -> {
			Singleton instance = Singleton.getInstance();
			System.out.println(instance);
		});
		Thread th2 = new Thread(() -> {
			Singleton instance = Singleton.getInstance();
			System.out.println(instance);
		});
		th.start();
		th1.start();
		th2.start();
	}

输出的结果为:
thread.Singleton@6c7d6e81
thread.Singleton@5c6ef4da
thread.Singleton@e50a5a2

不难发现这几个线程获取到的对象的内存地址并不相同,说明他们不是同一个对象。所以getInstance方法要加锁。关于多线程相关知识,有兴趣的小伙伴们可以看下我的另外一篇博客——Java多线程基础知识总结

public static Singleton getInstance() {
		synchronized(Singleton.class) {
			if (instance == null) {
				instance = new Singleton();
			}
		}
		
		return instance;
	}

再执行一下刚才的并发测试,发现打印的地址都是相同的。这样每个线程在进入getInstance方法后,都会先获取锁,没拿到锁的线程会阻塞,直到拿到锁才能执行。很显然,在高并发的情况下让每个线程都去竞争锁不太高效,比较高效的做法是:先判断一下,如果已经实例化了就直接返回,就不用去竞争锁了,代码如下:

	public static Singleton getInstance() {
		if (instance != null) {
			return instance;
		}
		synchronized(Singleton.class) {
			if (instance == null) {
				instance = new Singleton();
			}
		}
		
		return instance;
	}

这种写法叫DCL,即Double Checked Locking.
还有一种更极端的情况,就是“指令重排”问题。这里不打算介绍“指令重排”,看客大佬们可以自己去搜下。所以为了代码的极致严谨,给我们的instance属性加上volatile修饰,至此,懒汉模式的完整代码如下:

public class Singleton {
	
	private static volatile Singleton instance;
	
	private Singleton() {
	}
	
	public static Singleton getInstance() {
		if (instance != null) {
			return instance;
		}
		synchronized(Singleton.class) {
			if (instance == null) {
				instance = new Singleton();
			}
		}
		
		return instance;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章