关于单例的描述:在一个系统中,有且只有一个实例。
你的单例真的只生产一个实例吗?有办法破坏单例吗?
下面我们看看单例的几种写法,以及单实例是怎么被破坏的。
单例写法(一)、饿汉式单例
public class Singleton {
private static final Singleton SINGLETON = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return SINGLETON;
}
}
第一种饿汉式很简单,一般也是用的比较多的一种,虽说比较占用资源,现在内存什么的都很便宜了,就没必要计较这一点开销。如果在犹豫用什么方式去写,那么就用饿汉式吧。 但这种单例可以被反射破坏。我们知道,把构造方法私有化可以防止new的方式创建实例,但是日防夜防,却防不住反射。
只要调用一下newInstance方法就又可以产生新的实例了
Singleton.class.newInstance()
单例写法(二)、方法锁
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
第二种方法锁是懒汉式的单例写法,但是非常影响性能。一般不推荐使用。通过反射同样可以破坏单实例。
单例写法(三)、代码块锁,双重判断
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
第三种代码块锁的方式,一定要加上双重判断。并且要结合volatile关键字一起使用。volatile关键词使instance变量在多线程之间可见,解决了JVM重排序问题。
我们以为的对象的初始化顺序应该是这样的:
但它有可能是这样的:
java new操作是不具备原子性(原子性:一个操作不能被打断,要么全部执行完毕,要么不执行。)的,也就是说第二步,第三步顺序是不能保证的,有可能因为重排序问题被调整位置。 当第三步,出现在第二步时,已经分配了内存地址,那么对象就不是null,可以调用了,但是由于还没有调用构造方法初始化,又会报错。因此需要用volatile关键词来禁止重排序。
而双重if判断也是至关重要的。试想,当线程A访问getInstance方法,instance为null,进入了synchronized。这时线程B又进来了,由于线程A还没有new完这个对象, 因此线程B看到的instance依然是null,接着又进入了synchronized代码块,然后去new一个实例。这样线程A和线程B得到就不是同一个实例了,因此也无法达到单例的效果。
第三种方式也是无法避免反射来创建多实例的。
单例写法(四)、静态内部类方式
public class Singleton {
private static class Instance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Instance.INSTANCE;
}
}
第四种静态内部类的方式也是一种懒汉式的写法。但依然没法防住反射创建多个实例。
单例写法(五)、枚举方式
public enum Singleton {
INSTANCE;
public void method() {
}
}
第五种是推荐使用的一种方法,用枚举来实现单例,枚举可以避免反射构建新实例。当你对枚举进行newInstance调用的时候会出现以下错误:
Exception in thread "main" java.lang.InstantiationException: Singleton
at java.base/java.lang.Class.newInstance(Class.java:571)
at Test.main(Test.java:3)
Caused by: java.lang.NoSuchMethodException: Singleton.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3349)
at java.base/java.lang.Class.newInstance(Class.java:556)
... 1 more
可能看到这么多单例的写法有些眼花缭乱,越来越糊涂了。其实,在日常的开发中,任何一种都可以。只要注意别反射调用自己写的单例就好了。毕竟在java开发中有约定大于配置的说明。约定好就可以了,做到心里有数。