设计模式之单例模式
单例模式定义为某个类在系统中只能有一个实例。
这也是我当初认为是最简单的设计模式。
常用写法
以前一般写单例模式都是写成如下代码,
public class SmsInstance {
private final static SmsInstance smsInstance = new SmsInstance();
private SmsInstance(){
}
public static SmsInstance getSmsInstance(){
return smsInstance;
}
}
这种是饿汉式单例,就是先创建一个对象出来,需要的时候就返回这个对象,还有懒汉式的,在需要的时候才创建对象,如下所示。
private static SmsInstance smsInstance = null;
private SmsInstance(){
}
public static SmsInstance getSmsInstance() {
if (smsInstance == null) {
smsInstance = new SmsInstance();
}
return smsInstance;
}
但是考虑一点,为什么需要单例,单例就是你不想创建多个实例,只想系统中拥有一个实例。
首先,上面两种都可以通过反射创建实例,如下所示,获取到构造函数,然后设置setAccessible(true)即可。
Class clazz = SmsInstance.class;
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
constructor.setAccessible(true);
Object o = constructor.newInstance(null);
if(o instanceof SmsInstance){
SmsInstance s = (SmsInstance)o;
System.out.println(s);
}
Object o2 = constructor.newInstance(null);
if(o2 instanceof SmsInstance){
SmsInstance s = (SmsInstance)o2;
System.out.println(s);
}
}
当然应该没有人会丧心病狂到这种程度,放着好好的getSmsInstance不用,非要用反射newInstance出来。
而对于懒汉式,其实不通过反射也可以创建多个实例。
if (smsInstance == null) {
smsInstance = new SmsInstance();
}
分析懒汉式的代码,会发现有一个判空。
加入thread1判断为空,正好要new对象时,cpu时间到了,于是切换为thread2执行,此时thread2 new了对象出来后轮到thread1执行,thread1就会继续暂停之前的操作,new一个对象。
不过这种情况也很少发生,即使发生了有时候也没什么关系,只要保证多个实例对系统没影响就行。
线程安全的丧心病狂写法
public static synchronized SmsInstance getSmsInstance() {
if (smsInstance == null) {
smsInstance = new SmsInstance();
}
return smsInstance;
}
不过项目组中有些人为了保证只有一个实例,加了synchronized属性在方法上,这样虽然保证了线程安全,但是效率变慢了啊,线程需要排队了,真更加丧心病狂。
还有一种是双null检查,如下,使用volatile修饰对象,然后判断两次空,这样不会有任何线程安全问题。
private static volatile SmsInstance smsInstance;
private SmsInstance() {
}
public static synchronized SmsInstance getSmsInstance() {
if (smsInstance == null) {
synchronized (SmsInstance.class) {
if (smsInstance == null)
smsInstance = new SmsInstance();
}
}
return smsInstance;
}
静态内部类写法
private SmsInstance() {
}
public static SmsInstance getSmsInstance() {
return SmsInnerInstance.instance;
}
private static class SmsInnerInstance{
private final static SmsInstance instance = new SmsInstance();
}
这种是为了应对上面双Null检查的改写版,为了去掉线程可能排队的情况。
并不是太想用synchronized这个修饰词。
总结
单例模式,保证只有一个实例即可,有多个实例就想会不会影响系统执行,如果多个实例很少发生,也不会影响系统执行,那么简单的常用写法即可。
看过很多为了单例而写了各种判断的代码,其实大可不必,虽然看着牛逼,但很多人基本一眼看明白的就是常用写法。
上面无论是工厂模式、建造者模式还是单例模式,都是为了创建对象而存在,可以归类为创建型设计模式。