常见单例模式
单例模式(Singleton Design Pattern)保证一个类只能有一个实例。
单例模式的实现需要三个必要的条件:
- 单例类的构造函数必须是私有的,这样才能将类的创建权控制在类的内部,从而使得类的外部不能创建类的实例。
- 单例类通过一个私有的静态变量来存储其唯一实例。
- 单例类通过提供一个公开的静态方法,使得外部使用者可以访问类的唯一实例。
注意:
因为单例类的构造函数是私有的,所以单例类不能被继承。
另外,实现单例类时,还需要考虑三个问题:
- 创建单例对象时,是否线程安全。
- 单例对象的创建,是否延时加载。
- 获取单例对象时,是否需要加锁。
下面介绍五种实现单例模式的方式。
饿汉式
-
优点:
- 线程安全。
- 获取对象,不需要加锁。
-
缺点:
- 不支持延时加载。
-
代码:
/** *饿汉式 */ public class SingleDemoOne { private SingleDemoOne(){} private static final SingleDemoOne instance = new SingleDemoOne(); public static SingleDemoOne getInstance(){ return instance; } }
懒汉式
-
优点:
- 线程安全。
- 支持延时加载。
-
缺点:
- 获取对象,需要加锁。
-
代码:
/** * lazy init */ public class SingleDemoTwo { private SingleDemoTwo() {}; private static SingleDemoTwo instance; public static synchronized SingleDemoTwo getInstance(){ if(instance == null) instance = new SingleDemoTwo(); return instance; } }
Double Check + Volatile
问题1:为什么需要 double check ?
答:考虑以下情况,线程 A 和 B 同时阻塞在了 synchronized (SingleDemoThree.class)
,并且线程 A 获得了锁,线程 A 创建了一个实例,并将其赋值给 instance
,然后释放锁,并且拿这在这个引用在线程 A 中继续工作;此时 B 获得了锁,并且重新创建了一个实例(因为进入同步块之后,它没有判断 instance
是否为 null
),然后将引用赋值给 instance
,此时线程 A 引用的 instance
的实例已经被修改,但是它并不知情。
double check
可以解决上面这个问题。
问题2:为什么需要 volatile ?
答:JVM 中并没有规定实例化对象和赋值该实例的引用的先后顺序,也就是说 instance
不为 null
时,可能引用的对象实际上并没有创建完全,可能只是刚刚分配了内存(在创建的过程中)。此时其他线程拿到的对象可能是不完整的。
使用 volatile
关键字修饰 instance
之后,可以保证 instance
的写优先,就是说,如果其他线程在获取 instance
时,如果发现它正在被创建,会等到它创建完全之后,再返回它的引用。
-
优点:
- 线程安全。
- 支持延时加载。
- 获取时不需要加锁。
-
代码:
/** * double check + volatile */ public class SingleDemoThree { private SingleDemoThree() {} private volatile static SingleDemoThree instance; public static SingleDemoThree getInstance(){ if(instance == null){ synchronized (SingleDemoThree.class){ if(instance == null){ instance = new SingleDemoThree(); } } } return instance; } }
内部类
InnerHolder
是内部类,外部类 SingleDemoFour
被加载的是,并不会加载内部类。只有当调用 getInstance()
方法,主动去访问内部类静态成员变量,此时 InnerHolder
才会被加载,instance
才会被实例化。
-
优点:
- 线程安全。
- 支持延时加载。
- 获取时不需要加锁。
-
代码:
/** * inner class */ public class SingleDemoFour { private SingleDemoFour(){} public void fun(){ System.out.println("hello"); } private static class InnerHolder{ private final static SingleDemoFour instance = new SingleDemoFour(); } public static SingleDemoFour getInstance(){ return InnerHolder.instance; } }
枚举类
枚举类型不允许被继承;同时只能被实例化一次;但是不能懒加载。
-
优点:
- 线程安全。
- 获取时不需要加锁。
-
缺点:
- 不支持延时加载。
-
代码:
/** * enum */ public enum SingleDemoFive { INSTANCE; SingleDemoFive(){} public void fun(){ System.out.println("ok"); } public static SingleDemoFive getInstance(){ return INSTANCE; } }