Java设计模式(一)—— 单例模式
1、单例模式
确保一个类只有一个实例,并提供该实例的全局访问点。
1.1 饿汉式-线程安全
public class Hungry {
private static Hungry hungry = new Hungry();
private Hungry(){
}
public static Hungry getInstance(){
return hungry;
}
}
- 会造成资源的浪费
1.2 懒汉式-线程不安全
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
}
public static LazyMan getInstance(){
if(lazyMan == null){
return lazyMan = new LazyMan();
}
return lazyMan;
}
}
- 对象延迟实例化,没有用到该类就不会实例化该对象,从而节约资源;
- 这个实现是线程不安全的,如果多个线程同时进入
if(lazyMan) == null
,并且此时 lazyMan 为 null,那么会有多个线程执行return new LazyMan()
语句,这将导致实例化多次lazyMan
。
1.3 双重检验锁(DCL)-线程安全
public class LazyMan_Lock {
private volatile static LazyMan_Lock lazyMan_lock ;
private LazyMan_Lock(){
}
public static LazyMan_Lock getInstance(){
if(lazyMan_lock == null){
synchronized (LazyMan_Lock.class){
if(lazyMan_lock == null){
return lazyMan_lock = new LazyMan_Lock();
}
}
}
return lazyMan_lock;
}
}
如果只存在一个if判断
if(lazyMan_lock == null){
synchronized (LazyMan_Lock.class){
return lazyMan_lock = new LazyMan_Lock();
}
}
-
如果只用一个
if语句
判断,在lazyMan_lock == null
的情况下,如果两个线程同时进入了if语句,那么两个线程都会进入if语句块内。虽然if语句块内有加锁操作,但是两个线程都会执行lazyMan_lock = new LazyMan_Lock();
这条语句,只是先后问题,那么就会进行两次实例化。因此必须使用双重检验锁,也就是需要使用两个if语句:第一个if语句用来避免lazyMan_lock
未被实例化之后的加锁操作;第二个if语句进行了加锁,所以只能有一个线程进入,就不会出现lazyMan_lock == null
时两个线程同时进行实例化操作; -
lazyMan_lock
采用volatile
关键字修饰也是很有必要的,lazyMan_lock = new LazyMan_Lock();
这段代码其实是分为三步执行:- 为
lazyMan_lock
分配内存空间; - 初始化
lazyMan_lock
; - 将
lazyMan_lock
指向分配的内存地址;
- 为
但是由于JVM具有指令重排的特性,执行顺序有可能变成 1-3-2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程T1执行了1和3,此时T2调用getInstance()
后发现lazyMan_lock
不为空,因此返回lazyMan_lock
,但此时lazyMan_lock
还未被初始化。
使用volatile可以禁止JVM的指令重排,保证在多线程环境下也能正常运行。
1.4 静态内部类实现单例
public class Holder {
public Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
private static class InnerClass{
private static final Holder HOLDER = new Holder();
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Holder holder = Holder.getInstance();
Class clazz = Holder.class;
Constructor<Holder> constructor = (Constructor<Holder>) clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Holder holder1 = (Holder) constructor.newInstance();
System.out.println(holder == holder1);
}
}
- 当
Holder
类被加载时,静态内部类InnerClass
没有被加载进内部,只有当调用getInstance()
方法从而触发InnerClass.HOLDER
时InnerClass
才会被加载,初始化Holder
实例,并且 JVM 能确保 HOLDER 只被实例化一次; - 这种方法不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
1.5 枚举实现单例
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class EnumSingleTest{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Class clazz = EnumSingle.class;
Constructor<EnumSingle> c = clazz.getDeclaredConstructor(String.class,int.class);
c.setAccessible(true);
EnumSingle instance2 = c.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
========抛出异常
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.xiaojian.single.EnumSingleTest.main(EnumSingle.java:25)
-
可以防止反射破坏单例,使用反射获取实例会抛出异常:
Cannot reflectively create enum objects
。详情请看反射构建实例的方法newInstance()源码
转载自:https://juejin.cn/post/6996152515628630047