单例模式
单例模式
饿汉式单例模式
类一旦加载就创建一个单例(程序已启动就加载这个类),在调用 getInstance()
方法之前单例已经存在了。
缺点:及其浪费空间,因为在还未调用getInstance()
单例就以及创建了,这期间可能包含了一些及其占用空间的操作。
public class Hungry {
private Hungry() {
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式单例
// 懒汉式
public class LazyMan {
private LazyMan() {
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
上述这种懒汉式在单线程情况下是安全的,但在多线程情况下是不安全的,多个线程同时执行lazy = new LazyMan()
,这样就会创建多个对象。所以我们需要使用synchronized
关键字来保证同步。
// 懒汉式
public class LazyMan {
private LazyMan() {
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
上面的这种懒汉式在多线程的情况下大体来说是安全的,此时即使多个线程进入第一个if
语句块,也只有一个线程能拿到锁,等第一个线程创建完毕后才会释放锁,然后第二个线程拿到锁,进入第二个if
语句块,但在第一个线程的运行中,lazyMan
已经创建了,所以此时第二个线程不会创建新的对象。
但上述代码在多线程的情况下还是有些许问题,在实例化对象的过程中,这个操作不是一个原子性的,可能会发生指令重排。
实例化一个对象操作如下:
- 分配内存空间
- 执行构造代码块
- 把这个对象指向分配空间
正常步骤是1-2-3,当发生指令重排的时候,可能会变成1-3-2,这种情况就会出现一些问题。A线程成功拿到当前类的锁,先执行步骤1,然后执行步骤3,最后执行步骤2(步骤2还未执行完,B线程来了)。B线程在A线程执行步骤2的时候进入,发现lzayMan
此时不为空,因为此时已经分配了内存空间B线程直接返回lazyMan
,导致初始化的内容不是完整的,所有我们要使用volatile避免指令重排。
// 懒汉式
public class LazyMan {
private LazyMan() {
}
private volatile static LazyMan lazyMan;
// 双重检测锁 DCL懒汉式
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
/*
* 由于实例化对象不是一个原子性操作,这里可能发生指令重排
* 1. 分配内存空间
* 2. 执行构造代码块
* 3. 把这个对象指向分配的空间
* 若发生指令重排,可能会:
* A线程成功拿到当前类的锁,先执行步骤1,然后执行步骤3,最后执行步骤2
* B线程在A线程执行步骤2的时候进入,发现lzayMan此时不为空,因为此时已经分配了内存空间
* B线程直接返回lazyMan,导致初始化的内容不是完整的
* 所有要使用volatile避免指令重排
* */
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
静态内部类
当外部内被访问时,并不会加载内部类,所以只要不访问InnerClass
这个内部类, private static final Holder HOLDER = new Holder()
就不会实例化,这就相当于实现懒加载的效果,只有当InnerClass.HOLDER
被调用时访问内部类的属性,此时才会将对象进行实例化,这样既解决了饿汉模式下可能造成资源浪费的问题,也避免了了懒汉模式下的并发问题。
// 静态内部类
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
}
}
枚举
// 枚举,枚举本身也是一个Class类
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance() {
return INSTANCE;
}
}
字节码文件
package designPattern.singletonPattern;
public enum EnumSingleton {
INSTANCE;
private EnumSingleton() {
}
public EnumSingleton getInstance() {
return INSTANCE;
}
}
在字节码中显示,该类有一个空参的构造方法,我们可以通过反射来获取对象
class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingleton instance = EnumSingleton.INSTANCE;
// 编译器显示没有这个类的空参的构造方法
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingleton instance1 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
但此时却没有成功的创建出对象。说明字节码中实际上是没有空参的构造方法的。
使用javap -p
还原字节码文件,我们发现还原后的代码中,仍然有空参的构造方法,说明这里得到的结果也是不准确的。
通过jad
最终反编译的代码
D:\IdeaProjects\utils\Code\target\classes\designPattern\singletonPattern>jad -sjava EnumSingleton.class
Parsing EnumSingleton.class... Generating EnumSingleton.java
package designPattern.singletonPattern;
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(designPattern/singletonPattern/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public EnumSingleton getInstance()
{
return INSTANCE;
}
public static final EnumSingleton INSTANCE;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
在上述代码中,我们终于发现了一个不是空参的构造方法,于是我们可以使用这个构造方法,来通过放射获取枚举类的实例对象。
class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingleton instance = EnumSingleton.INSTANCE;
// 编译器显示没有这个类的空参的构造方法
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingleton instance1 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
得到以上运行结果,这也和反射中构造类规定的一样,不允许使用反射的方式去获取一个枚举类。