设计模式单例模式你用对了吗
Posted Misout的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式单例模式你用对了吗相关的知识,希望对你有一定的参考价值。
有一些对象,我们只希望只有一个实例,例如:线程池对象,缓存对象,对话框,日志对象,打印机对象等。事实上,这类对象只能有一个,如果多于一个,就会导致许多问题的产生,例如超多两个日志对象,会造成写日志文件失败的情况。
保证类的构造方法为私有,这样就无法通过new关键字主动创建对象。
对外提供一个静态方法,用于获取该类的独一无二的实例对象。
懒汉:在获取实例的静态方法被调用前,类的对象还未实例化,等到真正使用的时候(这个人比较懒,饿了的时候才开始做饭)再创建对象。属于懒加载的一种。
饿汉:在类被JVM加载的时候,类的对象就被创建了,也就是获取实例的静态方法被调用前,对象已经被创建好了(这个人一开始就很饿,早早的把饭做好了),属于预加载的一种。
1.懒汉非线程安全
public class Singleton1 {
private static Singleton1 instance;
private Singleton1() {}
public static Singleton1 getInstance() {
if(instance == null) {
instance = new Singleton1();
}
return instance;
}
}
非线程安全的写法,具体原因分析就不做过多赘述了,请看多线程场景下获取单例得到不同对象的测试代码和结果:
@Test
public void testLazymanNonThreadSafeSingleon() {
Runnable myRunable = new Runnable() {
@Override
public void run() {
Singleton1 instance = Singleton1.getInstance();
System.out.println(instance);
}
};
Thread thread1 = new Thread(myRunable);
Thread thread2 = new Thread(myRunable);
thread1.start();
thread2.start();
}
com.misout.singleton.Singleton1@1b8feeb
com.misout.singleton.Singleton1@c44cf9
推荐程度:不推荐
2.懒汉线程安全-同步方法
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
public static synchronized Singleton2 getInstance() {
if(instance == null) {
instance = new Singleton2();
}
return instance;
}
}
在方法上加了synchronized关键字,能保证获取的对象都是同一个。
推荐程度:可用,但不推荐。虽然线程安全,但synchronized关键字加载整个方法上,性能太差,在高并发性能敏感的场景下不适用。
3.懒汉线程安全-同步代码块
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {}
public static Singleton3 getInstance() {
synchronized (Singleton3.class) {
if(instance == null) {
instance = new Singleton3();
}
}
return instance;
}
}
这种写法,只是简单的把加在方法上的synchronized关键字移到了方法内部,性能上没有任何优化,一样很差。
推荐程度:可用,但不推荐
下面的写法是否线程安全?
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {}
public static Singleton3 getInstance() {
if(instance == null) {
synchronized (Singleton3.class) {
instance = new Singleton3();
}
}
return instance;
}
}
很多面试官会哪这种写法和上面的写法考察应聘者,比如我就很喜欢。但不幸的是,很多应聘者都回答错误,让我很失望。答案当然是否定的。下面用测试用例来测试下到底是否线程安全:
@Test
public void testLazymanSingleon() {
Runnable myRunable = new Runnable() {
@Override
public void run() {
Singleton3 instance = Singleton3.getInstance();
System.out.println(instance);
}
};
Thread thread1 = new Thread(myRunable);
Thread thread2 = new Thread(myRunable);
thread1.start();
thread2.start();
}
com.misout.singleton.Singleton3@e8bc6d
com.misout.singleton.Singleton3@d9b5b
原因很简单:if判断条件并不能保证多线程下只有一个线程在执行。
4.懒汉线程安全-双重检测锁
双重检测锁的写法是对方法上加锁或代码块加锁的一种性能优化,多增加一次if判断,能减少进入同步代码块的线程数从而降低竞争锁的激烈程度,减少线程阻塞切换上下文环境带来的性能损耗。
public class Singleton4 {
private static volatile Singleton4 instance;
private Singleton4() {}
public static Singleton4 getInstance() {
if(instance == null) {
synchronized (Singleton4.class) {
if (instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}
}
即达到了懒加载的目的,也优化了部分性能。
推荐程度:推荐。
这里我们继续扩展下,上面代码中,变量声明时增加了关键字volatile,这里会是面试官经常重点考察的点,比如我就很喜欢问不加volatile有什么问题?
很多应聘者答,不加volatile关键字就无法保证对象的可见性,对象赋值后有可能是空的,但这种回答有点牵强。
synchronized关键字已经保证了对象的可见性,volatile的作用在这里并非可见性。
多线程场景下测试没有volatile关键字的代码和测试结果:
测试代码:
@Test
public void testDoubleCheckSingleon() {
Runnable myRunable = new Runnable() {
@Override
public void run() {
Singleton4 instance = Singleton4.getInstance();
System.out.println(instance);
}
};
Thread thread1 = new Thread(myRunable);
Thread thread2 = new Thread(myRunable);
thread1.start();
thread2.start();
}
"D:\Program Files (x86)\Java\jdk1.8.0_102\bin\java.exe" -ea ...省略
com.misout.singleton.Singleton4@1ce9ad6
Process finished with exit code 0
5.饿汉线程安全-静态常量写法
public class Singleton5 {
private static final Singleton5 instance = new Singleton5();
private Singleton5() {}
public static Singleton5 getInstance() {
return instance;
}
}
优点:实现简单,无线程同步问题。
缺点:在类装载时完成实例化。若该实例一直未被使用,则会造成资源浪费。
推荐程度:推荐。
6.饿汉线程安全-静态代码块写法
public class Singleton6 {
private static final Singleton6 instance;
static {
instance = new Singleton6();
}
private Singleton6() {}
public static Singleton6 getInstance() {
return instance;
}
}
优点:无线程同步问题,线程安全。
缺点:类装载时创建实例,无Lazy Loading。实例一直未被使用时,会浪费资源
推荐程度:推荐。
7.懒汉线程安全-静态内部类写法
public class Singleton7 {
static class Inner {
private static final Singleton7 instance = new Singleton7();
}
private Singleton7() {}
public static Singleton7 getInstance() {
return Inner.instance;
}
}
优点:无线程同步问题,实现了懒加载(Lazy Loading)。因为只有调用
getInstance
时才会装载内部类,才会创建实例。同时因为使用内部类时,先调用内部类的线程会获得类初始化锁,从而保证内部类的初始化(包括实例化它所引用的外部类对象)线程安全。即使内部类创建外部类的实例Singleton INSTANCE = new Singleton()
发生指令重排也不会引起双重检查(Double-Check)下的懒汉模式中提到的问题,因此无须使用volatile
关键字。缺点:NA
推荐程度:推荐。
8.枚举写法
public enum Singleton8 {
instance;
public static Singleton8 getInstance() {
return instance;
}
}
优点:枚举本身是线程安全的,且能防止通过反射和反序列化创建多实例。
缺点:使用的是枚举,而非类。
上文的八种写法,除了枚举写法无法通过反射来破坏单例外,其他方式都可以通过反射破坏单例。但在实际应用场景下,这种情况比较少见,故而忽略。
推荐阅读
Misout的博客
长按二维码关注我
以上是关于设计模式单例模式你用对了吗的主要内容,如果未能解决你的问题,请参考以下文章