单例模式
单例模式是 Java 中罕用的设计模式之一,属于设计模式三大类中的创立型模式。在运行期间,保障某个类仅有一个实例,并提供一个拜访它的全局拜访点。单例模式所属类的构造方法是公有的,所以单例类是不能被继承的。实现线程平安的单例模式有以下几种形式:
1.饿汉式

public class Singleton {     private static Singleton instance = new Singleton();     private Singleton() {    }     public static Singleton getInstance() {        return instance;    } }

这是实现一个平安的单例模式的最简略粗犷的写法,之所以称之为饿汉式,是因为肚子饿了,想马上吃到货色,不想期待生产工夫。在类被加载的时候就把Singleton实例给创立进去供应用,当前不再扭转。

长处:实现简略, 线程平安,调用效率高(无锁,且对象在类加载时就已创立,可间接应用)。

毛病:可能在还不须要此实例的时候就曾经把实例创立进去了,不能延时加载(在须要的时候才创建对象)。

2.懒汉式

public class Singleton {        private static Singleton instance = null;     private Singleton() {    }     //如果没有synchronized,则线程不平安    public static synchronized Singleton getInstance() {//synchronized也能够写在办法里,造成同步代码块        if (instance == null) {            instance = new Singleton();        }        return instance;    }    }

相比饿汉式,懒汉式显得没那么“饿”,在真正须要的时候再去创立实例。

长处:线程平安,能够延时加载。

毛病:调用效率不高(有锁,且须要先创建对象)。

3.懒汉式改良版(双重同步锁)

public class Singleton {     private static volatile Singleton singleton;     private Singleton() {    }     public static Singleton getInstance() {        if (singleton == null) {            synchronized (Singleton.class) {                if (singleton == null) {                    singleton = new Singleton();                }            }        }        return singleton;    }}

应用了double-check,即check-加锁-check,缩小了同步的开销

第2种懒汉式的效率低在哪里呢?第二种写法将synchronized加在了办法上(或者写在办法里),在单例对象被创立后,因为办法加了锁,所以要等以后线程失去对象开释锁后,下一个线程才能够进入getInstance()办法获取对象,也就是线程要一个一个的去获取对象。而采纳双重同步锁,在synchronized代码块前加了一层判断,这使得在对象被创立之后,多线程不需进入synchronized代码块中,能够多线程同时并发拜访获取对象,这样效率大大提高。

在创立第一个对象时候,可能会有线程1,线程2两个线程进入getInstance()办法,这时对象还未被创立,所以都通过第一层check。接下来的synchronized锁只有一个线程能够进入,假如线程1进入,线程2期待。线程1进入后,因为对象还未被创立,所以通过第二层check并创立好对象,因为对象singleton是被volatile润饰的,所以在对singleton批改后会立刻将singleton的值从其工作内存刷回到主内存以保障其它线程的可见性。线程1完结后线程2进入synchronized代码块,因为线程1曾经创立好对象并将对象值刷回到主内存,所以这时线程2看到的singleton对象不再为空,因而通过第二层check,最初获取到对象。这里volatile的作用是保障可见性,同时也禁止指令重排序,因为上述代码中存在管制依赖,多线程中对管制依赖进行指令重排序会导致线程不平安。

长处:线程平安,能够延时加载,调用效率比2高。

4.外部动态类
public class Singleton {

private Singleton() {    }public static Singleton getInstance() {    return SingletonFactory.instance;}private static class SingletonFactory {    private static Singleton instance = new Singleton();}

}
动态外部类只有被被动调用的时候,JVM才会去加载这个动态外部类。外部类首次加载,会初始化动态变量、动态代码块、静态方法,但不会加载外部类和动态外部类。

长处:线程平安,调用效率高,能够延时加载。

仿佛动态外部类看起来曾经是最完满的办法了,其实不是,可能还存在反射攻打和反序列化攻打。

a)反射攻打

public static void main(String[] args) throws Exception {    Singleton singleton = Singleton.getInstance();    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();    constructor.setAccessible(true);    Singleton newSingleton = constructor.newInstance();    System.out.println(singleton == newSingleton);}

运行后果:false

通过后果看,这两个实例不是同一个,违反了单例模式的准则。

b)反序列化攻打

引入依赖:

<dependency>    <groupId>org.apache.commons</groupId>    <artifactId>commons-lang3</artifactId>    <version>3.8.1</version></dependency>

这个依赖提供了序列化和反序列化工具类。

Singleton类实现java.io.Serializable接口。

public class Singleton implements Serializable {     private static class SingletonHolder {        private static Singleton instance = new Singleton();    }     private Singleton() {     }     public static Singleton getInstance() {        return SingletonHolder.instance;    }     public static void main(String[] args) {        Singleton instance = Singleton.getInstance();        byte[] serialize = SerializationUtils.serialize(instance);        Singleton newInstance = SerializationUtils.deserialize(serialize);        System.out.println(instance == newInstance);    } }

运行后果:false

5.枚举
最佳的单例实现模式就是枚举模式。写法简略,线程平安,调用效率高,能够人造的避免反射和反序列化调用,不能延时加载。
public enum Singleton {

INSTANCE;public void doSomething() {    System.out.println("doSomething");}

}
调用办法:

public class Main {     public static void main(String[] args) {        Singleton.INSTANCE.doSomething();    } }

间接通过Singleton.INSTANCE.doSomething()的形式调用即可。

枚举如何实现线程平安?反编译后能够发现,会通过一个类去继承改枚举,而后通过动态代码块的形式在类加载时实例化对象,与饿汉式相似。https://blog.csdn.net/wufalia...

如何做到避免反序列化调用?每一个枚举类型及其定义的枚举变量在JVM中都是惟一的,Java做了非凡的规定,枚举类型序列化和反序列化进去的是同一个对象。

除此之外,枚举还能够避免反射调用。

**综上,线程平安的几种单例模式比拟来看:
枚举(无锁,调用效率高,能够避免反射和反序列化调用,不能延时加载)> 动态外部类(无锁,调用效率高,能够延时加载) > 双重同步锁(有锁,调用效率高于懒汉式,能够延时加载) > 懒汉式(有锁,调用效率不高,能够延时加载) ≈ 饿汉式(无锁,调用效率高,不能延时加载)

ps:只有枚举能避免反射和反序列化调用**