一个单身汉的成长之路--单例模式
在编码中单例是很常用的设计模式,通常我们使用的Spring创建的Bean就是默认单例的,这是一个常用又重要的模式。
单例模式是一个创建型的设计模式,它保证程序进程中只有一个实例被创建,所以很容易想到,这个类的构造器必须是private的。
饿汉式单例
最简单的单例模式是饿汉式单例,它的代码如下:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
System.out.println("我被初始化了");
}
public static Singleton getInstance() {
return instance;
}
}
这种写法固然简单又安全,但是无疑会造成一个问题,如果写一个空的main方法试试,就会发现,即使什么都不做,这个类的构造函数也会被创建,如果这个类需要加载较多资源但是又没有被调用到的话,显然会浪费内存。
那么如果说在getInstance()方法里构造这个对象会怎么样呢
懒汉式单例
下面是一个懒汉式单例的写法:
public class Singleton {
private static Singleton instance = null;
private Singleton() {
System.out.println("我被初始化了");
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种写法最大的问题就是不安全,我们可以做个测试:
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
Singleton.getInstance();
},"Thread" + i).start();
}
}
这个测试执行结果可能如下:
Thread0
Thread1
我被初始化了
我被初始化了
Thread2
Thread4
Thread3
...
线程安全的懒汉
也就是说,多线程情况下获得了两个实例,显然这样不正确。那么我们可以实现线程安全的懒汉式单例吗?
很简单,只需要加一个synchronized关键字不就好了吗
public class Singleton {
private static Singleton instance = null;
private Singleton() {
System.out.println("我被初始化了");
}
public synchronized static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
这样写最大的问题就是性能极差,每次都进同步代码块,多线程情况下只能排队获取实例。
更进一步的写法是双重检查:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
System.out.println("我被初始化了");
}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这种写法虽然解决了问题,但是为了保证线程之间的instance引用的可见性,使用了volatile修饰符,volatile会屏蔽JVM的指令重排序等优化,也会造成一定程度的性能下降。
最好的单身汉
那么有没有完美的解决办法呢?既需要懒加载,又需要线程安全,还需要保证性能。
答案是有的:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
System.out.println("我被初始化了");
}
private static class Holder {
private static Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return Holder.singleton;
}
}
继续用上面的main方法做个测试,得到的结果如下:
Thread0
Thread1
Thread2
Thread3
Thread4
我被初始化了
Thread5
Thread6
Thread7
Thread8
Thread9
可以看到虽然有并发争夺,但是只被初始化了一次,并且是在getInstance()被调用之后初始化的。
这种方式,利用了Java虚拟机的特性来保证线程安全性,既可以实现懒加载,又可以保证线程安全,还不影响性能,是一种较好的单例实现方式。

浙公网安备 33010602011771号