单例模式的几种写法

单例模式三个要素:

1.私有构造函数,防止其他代码实例化对象;

2.提供一个属性,保存那个唯一的实例化对象;

3.提供一个公共方法,以便别人获取到实例化对象;

饿汉式

public class Singleton01 {
    // 私有化构造函数
    private Singleton01() {}
    // 静态属性,指向单例对象
    private static final Singleton01 INSTANCE = new Singleton01();
    //公共方法,以便别人获取对象
    public static Singleton01 getInstance() {
        return INSTANCE;
    }
}

优点:写法简单,线程安全。

缺点:消耗资源,不管对象是否会用到,都会实例化对象出来。

懒汉式

public class Singleton02{
    // 私有化构造函数
    private Singleton02(){}
    // 静态属性,指向单例对象
    private final final Singleton02 INSTANCE;
    // 公共方法,以便别人获取对象
    public static Singleton02 getInstance(){
        if(INSTANCE == null){
            INSTANCE = new Singleton02();
        }
        return INSTANCE;
    }
}

优点:写法简单,节省资源,只有用到这个对象的时候才会进行实例化。

缺点:存在线程安全问题。

简单加锁

public class Singleton02 {
    // 私有化构造函数
    private Singleton02() {}
	// 静态属性,指向单例对象
    private static Singleton02 INSTANCE;
    // 加了synchronized关键字
    public synchronized static Singleton02 getINSTANCE() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton02();
        }
        return INSTANCE;
    }
}

优点:线程安全,写法简单。

缺点:每次获取对象都进行加锁,存在性能问题。

双重检查

public class Singleton03{
    // 私有化构造函数
    private Singleton03() {}
	// 静态属性,指向单例对象(注意这里加了volatile关键字,否则在多线程情况下指令重排会出问题)
    private static volatile Singleton03 INSTANCE;
    public static Singleton03 getINSTANCE() {
        // 只有在创建新对象的时候才进行加锁
        if (INSTANCE == null) {
            synchronized (Singleton03.class){
                // 这里还要再判断一下,否则锁释放之后其他线程还是要实例化对象
                 if (INSTANCE == null){
                     INSTANCE = new Singleton03();
                 }
            }
       }
       return INSTANCE;
    }
}

优点:线程安全,相比上一种节约资源

缺点:已经很美好了,已经大大减少了获取锁的次数,不过还是可以更好。

静态内部类

public class Singleton04 {
    // 私有化构造函数
    private Singleton04() {}
    // 在内部类声明一个静态属性
    private static class InnerHolder {
        static final Singleton04 INSTANCE = new Singleton04();
    }
    // 公共方法
    public static Singleton04 getInstance() {
        return InnerHolder.INSTANCE;
    }
}

因为JVM保证了内部类的线程安全,即一个内部类在整个程序中不会被重复加载,并且如果你没有使用到内部类的话,是不会加载这个内部类的。这就非常巧妙的实现了线程安全以及节约资源的好处!

优点:节约资源、线程安全、性能好

缺点:会被序列化或者反射破坏单例

以上的几种写法均存在被序列化或反射破坏的情况,可以进行一下优化

public class Main implements Serializable, Cloneable {
    private static final long serialVersionUID = 6125990676610180062L;
    private static Main main;
    private static boolean isFirstCreate = true;
    private Main(){
        /*防止反射破坏单例*/
        if(isFirstCreate){
            synchronized (Main.class) {
                if(isFirstCreate){
                    isFirstCreate = false;
                }
            }
        }else{
            throw new RuntimeException("已经实例化一次, 不能再实例化");
        }
    }

    public  static Main getInstance(){
        if (main == null) {
            synchronized (Main.class) {
                if (main == null) {
                    main = new Main();
                }
            }
        }
        return main;
    }
    /*防止克隆破坏单例*/
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return main;
    }
    /*防止序列化破坏单例*/
    private Object readResolve() {
        // TODO Auto-generated method stub
        return main;
    }


    public static void main(String[] args) throws Exception{
        Main main = Main.getInstance();
        System.out.println("singleton的hashCode:"+main.hashCode());
        //通过克隆获取
        Main clob = (Main) Main.getInstance().clone();
        System.out.println("clob的hashCode:"+clob.hashCode());
        //通过序列化,反序列化获取
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(Main.getInstance());
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        Main serialize = (Main) ois.readObject();
        if (ois != null) ois.close();
        if (bis != null) bis.close();
        if (oos != null) oos.close();
        if (bos != null) bos.close();
        System.out.println("serialize的hashCode:"+serialize.hashCode());
        //通过反射获取
        Constructor<Main> constructor = Main.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Main reflex = constructor.newInstance();
        System.out.println("reflex的hashCode:"+reflex.hashCode());
    }
}

枚举实现

一种简单巧妙的方法,根本不存在构造方法,当然也就不会被反射和序列化破坏

public enum Singleton05 {
    // 实例
    INSTANCE;
    // 公共方法
    public Singleton05 getInstance() {
        return INSTANCE;
    }
}
原文地址:https://www.cnblogs.com/hanstrovsky/p/14168209.html