简朴的单例形式实在也不简朴_玖富娱乐主管发布


玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。

单例形式能够说只若是一个及格的开辟都会写,然则若是要穷究,小小的单例形式能够牵扯到许多器械,好比 多线程是不是平安,是不是懒加载,机能等等。另有你晓得几种单例形式的写法呢?怎样防备反射损坏单例形式?本日,我就花一章内容来说说单例形式。

关于单例形式的观点,在这里就不在论述了,置信每一个小伙伴都管窥蠡测。

我们直接进入正题:

饿汉式

public class Hungry {
    private Hungry() {
    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance() {
        return hungry;
    }
}

饿汉式是最简朴的单例形式的写法,包管了线程的平安,在很长的时间里,我都是饿汉形式来完成单例的,因为够简朴,厥后才晓得饿汉式会有一点小题目,看下面的代码:

public class Hungry {
    private byte[] data1 = new byte[1024];
    private byte[] data2 = new byte[1024];
    private byte[] data3 = new byte[1024];
    private byte[] data4 = new byte[1024];
    
    private Hungry() {
    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance() {
        return hungry;
    }
}

在Hungry类中,我界说了四个byte数组,当代码一运转,这四个数组就被初始化,而且放入内存了,若是长时间没有用到getInstance要领,不需要Hungry类的工具,这不是一种糟蹋吗?我愿望的是 只要用到了 getInstance要领,才会去初始化单例类,才会加载单例类中的数据。以是就有了 第二种单例形式:懒汉式。

懒汉式(DCL)

public class LazyMan {
    private LazyMan() {
    }

    private static LazyMan lazyMan;

    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
}

DCL懒汉式的单例,包管了线程的平安性,又相符了懒加载,只要在用到的时刻,才会去初始化,挪用效力也对照高,然则这类写法在极度状况照样能够会有肯定的题目。因为

 lazyMan = new LazyMan();

不是原子性操纵,最少会经由三个步调:

  1. 分派内存
  2. 实行组织要领
  3. 指向地点

因为指令重排,致使A线程实行 lazyMan = new LazyMan();的时刻,能够先实行了第三步(还没实行第二步),此时线程B又进来了,发明lazyMan已不为空了,直接返回了lazyMan,而且背面使用了返回的lazyMan,因为线程A还没有实行第二步,致使此时lazyMan还不完整,能够会有一些意想不到的毛病,以是就有了下面一种单例形式。

懒汉式(Volatile)

这类单例形式只是在上面DCL单例形式增添一个volatile关键字来制止指令重排:

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;
    }
}

持有者

public class Holder {
    private Holder() {
    }

    public static Holder getInstance() {
        return InnerClass.holder;
    }

    private static class InnerClass {
        private static final Holder holder = new Holder();
    }
}

这类体式格局是第一种饿汉式的革新版本,一样也是在类中界说static变量的工具,而且直接初始化,不过是移到了静态内部类中,非常奇妙。既包管了线程的平安性,同时又知足了懒加载。

万恶的反射

万恶的反射上台了,反射是一个对照王道的器械,疏忽private润饰的组织要领,能够直接在表面newInstance,损坏我们辛辛苦苦写的单例形式。

 public static void main(String[] args) {
        try {
            LazyMan lazyMan1 = LazyMan.getInstance();
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
            LazyMan lazyMan2 = declaredConstructor.newInstance();
            System.out.println(lazyMan1.hashCode());
            System.out.println(lazyMan2.hashCode());
            System.out.println(lazyMan1 == lazyMan2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

我们离别打印出lazyMan1,lazyMan2的hashcode,lazyMan1是不是相称lazyMan2,效果不言而喻:

-玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。-

那末,怎样处理这类题目呢?

public class LazyMan {
    private LazyMan() {
        synchronized (LazyMan.class) {
            if (lazyMan != null) {
                throw new RuntimeException("不要试图用反射损坏单例形式");
            }
        }
    }

    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
}

在私有的组织函数中做一个推断,若是lazyMan不为空,申明lazyMan已被建立过了,若是一般挪用getInstance要领,是不会涌现这类事变的,以是直接抛出非常:

然则这类写法照样有题目:

上面我们是先一般的挪用了getInstance要领,建立了LazyMan工具,以是第二次用反射建立工具,私有组织函数内里的推断起作用了,反射损坏单例形式失利。然则若是损坏者痛快不先挪用getInstance要领,一上来就直接用反射建立工具,我们的推断就不见效了:

 public static void main(String[] args) {
        try {
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
            LazyMan lazyMan1 = declaredConstructor.newInstance();
            LazyMan lazyMan2 = declaredConstructor.newInstance();
            System.out.println(lazyMan1.hashCode());
            System.out.println(lazyMan2.hashCode());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

那末怎样防备这类反射损坏呢?

public class LazyMan {
    private static boolean flag = false;
    private LazyMan() {
        synchronized (LazyMan.class) {
            if (flag == false) {
                flag = true;
            } else {
                throw new RuntimeException("不要试图用反射损坏单例形式");
            }
        }
    }
    private volatile static LazyMan lazyMan;
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
}

在这里,我界说了一个boolean变量flag,初始值是false,私有组织函数内里做了一个推断,若是flag=false,就把flag改成true,然则若是flag即是true,就申明有题目了,因为一般的挪用是不会第二次跑到私有组织要领的,以是抛出非常:

看起来很优美,然则照样不克不及完整防备反射损坏单例形式,因为能够应用反射修正flag的值。

看起来并没有一个很好的计划去制止反射损坏单例形式,以是轮到我们的罗列上台了。

罗列

public enum EnumSingleton {
    instance;
    public EnumSingleton getInstance(){
        return instance;
    }
}

罗列是现在最引荐的单例形式的写法,因为充足简朴,不需要开辟本身包管线程的平安,同时又能够有用的防备反射损坏我们的单例形式,我们能够看下newInstance的源码:


重点就是红框中圈出来的局部,若是罗列去newInstance就直接抛出非常了。

好了,这章的内容就完毕了,下次再有人问你单例形式,再也不消害怕了。

-玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。