单例模式
** 作用: 确保一个类只有一个实例,并提供该实例的全局访问点**
1.懒汉模式 – 线程不安全 :
优点: singletonInstance 被延迟实例化,没有用到时不会被实例化,从而节省资源.
缺点:线程不安全,当多个线程进入if(singletonInstance == null) 代码块中,从而多次实例化singletonInstance.
public class Singleton {
public static Singleton singletonInstance;
private Singleton(){
}
public Singleton getSingletonInstance(){
if(singletonInstance == null){
singletonInstance = new Singleton();
}
return singletonInstance;
}
}
2.饥饿模式 – 线程安全
优点: 线程安全 (线程不安全是因为被实例化多次,直接实例化就能避免)
缺点: 失去了延迟实例化的节省资源的好处.
public class Singleton {
public static Singleton singletonInstance = new Singleton();
}
3.懒汉式 – 线程安全
缺点:就算被实例化了,但是每次有线程进入方法后,其他线程都需要等待,从而存在性能问题.
public class Singleton {
public static Singleton singletonInstance ;
private Singleton(){
}
public static synchronized Singleton getSingletonInstance(){
if(singletonInstance == null){
singletonInstance = new Singleton();
}
return singletonInstance;
}
}
4. 双重校验锁 – 线程安全
public class Singleton {
public static volatile Singleton singletonInstance ;
private Singleton(){
}
public static Singleton getSingletonInstance(){
if(singletonInstance == null){
synchronized (Singleton.class){
if(singletonInstance == null){
singletonInstance = new Singleton();
}
}
}
return singletonInstance;
}
}
4.1 为什么要双重校验?
考虑以下的实现:
if(singletonInstance == null){
synchronized (Singleton.class){
singletonInstance = new Singleton();
}
}
如果只有一重检验,假设有两个线程进入了if(singletonInstance == null) 块,虽然有锁,但是两个线程还是会顺序执行实例化语句,只是执行先后的问题.所以就需要两次校验.
4.2 为什么使用volatile 关键字?
uniqueInstance 采用 volatile 关键字修饰也是很有必要的,**singletonInstance = new Singleton();**这段代码其实是分为三步执行:
1.为 uniqueInstance 分配内存空间
2.初始化 uniqueInstance
3.将 uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getSingletonInstance() 后发现singletonInstance不为空,因此返回 singletonInstance,但此时singletonInstance还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
5.静态内部类实现:
当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getSingletonInstance() 方法从而触发 SingletonHolder.singletonInstance; 时 SingletonHolder 才会被加载,此时初始化 singletonInstance 实例,并且 JVM 能确保singletonInstance 只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
public class Singleton {
private Singleton(){
}
private static class SingletonHolder{
public static Singleton singletonInstance = new Singleton();
}
public static Singleton getSingletonInstance(){
return SingletonHolder.singletonInstance;
}
}
6.枚举实现:
public enum Singleton {
INSTANCE;
private String objName;
public String getObjName() {
return objName;
}
public void setObjName(String objName) {
this.objName = objName;
}
public static void main(String[] args) {
// 单例测试
Singleton firstSingleton = Singleton.INSTANCE;
firstSingleton.setObjName("firstName");
System.out.println(firstSingleton.getObjName());
Singleton secondSingleton = Singleton.INSTANCE;
secondSingleton.setObjName("secondName");
System.out.println(firstSingleton.getObjName());
System.out.println(secondSingleton.getObjName());
// 反射获取实例测试
try {
Singleton[] enumConstants = Singleton.class.getEnumConstants();
for (Singleton enumConstant : enumConstants) {
System.out.println(enumConstant.getObjName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//
// 该实现在多次序列化再进行反序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,
// 并且实现序列化和反序列化的方法。
//
// 该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,
// 然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。
// 该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。