单例模式应该都不陌生,被广泛使用的设计模式之一,在应用这个模式时,单例对象的类必须保证只有一个实例存在。
下面将会为大家介绍几种单例实现的方式,虽然实现方式有差异,但是核心原理都是:
1、将构造函数私有化
2、通过静态方法获取一个唯一的实例
3、在获取过程中保证线程安全
4、防止反序列化导致重新生成实例对象
线程不安全的单例
这种实现方式是线程不安全的,非常不推荐这种做法
public class Signleton {
private static Signleton signleton;
private Signleton() {
}
/**
* 第一中方式:缺点,多线程访问时不安全
*
* @return
*/
public static Signleton getInstance() {
if (signleton == null) {
signleton = new Signleton();
}
return signleton;
}
/**
* 加入该方法杜绝在反序列化时重写生成对象
*
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return signleton;
}
}
线程安全的单例
这种方式是线程安全的,但是每次访问getInstance()方法时都会进行同步控制,效率低
public class Signleton {
private static Signleton signleton;
private Signleton() {
}
/**
* 第二中方式 加上同步关键字,解决了多线程访问安全的问题
* 缺点:会降低性能,因为一旦设置好signleton变量,就不需要同步了,之后每次调用该方法会降低性能
*
* @return
*/
public static synchronized Signleton getInstance() {
if (signleton == null) {
signleton = new Signleton();
}
return signleton;
}
/**
* 加入该方法杜绝在反序列化时重写生成对象
*
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return signleton;
}
}
双重锁的单例模式
该方式避免了每次调用getInstance()方法都进行同步控制
public class Signleton {
private static Signleton signleton;
private Signleton() {
}
/**
* 第三种方式, 双重检查加锁,首先检查实例是否已经创建,如果未创建才进行同步控制,
* 这样只有第一次会同步,提高了性能
* 这种方式是错误的!!!!!或导致错误的结果,请看文章最后
*
* @return
*/
//public static Signleton getInstance() {
// if (signleton == null) {
// synchronized (Signleton.class) {
// if (signleton == null) {
// signleton = new Signleton();
// }
// }
// }
// return signleton;
//}
/**
* 加入该方法杜绝在反序列化时重写生成对象
*
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return signleton;
}
}
但是由于signleton=new Signleton()不是原子操作,由于线程调度的原因会在某些情况下出现失效的问题,不过只需要加上volatile即可(private volatile static Signleton signleton;)。
饿汉单例模式
该方式会在类加载时就初始化signleton。
public class Signleton {
private static Signleton signleton=new Signleton();//对应第四中方式
private Signleton() {
}
/**
* 第四种方式,如果应用程序总是创建并使用单例实例,可以在镜头初始化器中创建单例, 使用时直接返回
*
* @return
*/
public static Signleton getInstance() {
return signleton;
}
/**
* 加入该方法杜绝在反序列化时重写生成对象
*
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return signleton;
}
}
静态内部类单例模式
该模式在类加载时不会初始化signleton,只有在调用get方法时才初始化
public class Signleton {
/**
* 第五种方式,使用静态内部类实现单例,相比饿汉模式不会在第一次加载Signleton类时就初始化sInstance,
* 只有在调用getInstance时才会初始化
*/
public static Signleton getInstance() {
return SignletonHolder.sInstance;
}
private static class SignletonHolder {
private static final Signleton sInstance = new Signleton();
}
/**
* 加入该方法杜绝在反序列化时重写生成对象
*
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return SignletonHolder.sInstance;
}
}
枚举单例
该方式写法简单,且默认枚举实例的创建时线程安全的,并且在任何情况下它都是一个单例
public enum SingletonEnum {
INSTANCE;
// TODO 一些类的方法
public void doSomthing() {
}
}
第三种方式存在的问题:
public class DoubleCheckedLocking { // 1
private static Instance instance; // 2
public static Instance getInstance() { // 3
if (instance == null) { // 4:第一次检查
synchronized (DoubleCheckedLocking.class) { // 5:加锁
if (instance == null) // 6:第二次检查
instance = new Instance(); // 7:问题的根源出在这里
} // 8
} // 9
return instance; // 10
} // 11
}
在线程执行到第4行,代码读取到instance不为null时,instance引用的对象有可能还没有完成初始化。
instance= new Instance() 可以分解为三步
memory = allocate(); // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory; // 3:设置instance指向刚分配的内存地址
2和3在某些JIT编译器上是可以被重排序的,这样会导致这样的可能性,线程1正在执行第7步时,线程B执行了第四步instannce!=null。线程B将访问instance引用的对象,由于2,3进行了重排序,导致线程B访问到了一个未初始化的对象
可使用volatile禁止2,3的重排序(JDK1.5以后增强了volatile的语义)
public class SafeDoubleCheckedLocking {
private volatile static Instance instance;
public static Instance getInstance() {
if (instance == null) {
synchronized (SafeDoubleCheckedLocking.class) {
if (instance == null)
instance = new Instance(); // instance为volatile,现在没问题了
}
}
return instance;
}
}