单例模式如何在多线程下保证单例

单例模式的实现方式:

1、 使用饿汉模式加载或使用static代码块

public class SingletonHungry {

    // 使用饿汉模式加载

    private static SingletonHungry instance = new SingletonHungry();

//    private static SingletonHungry instance;

//    static {

//        instance = new SingletonHungry();

//        System.out.println("使用static代码块实现单例,SingletonHungry.hashCode=" + instance.hashCode());

//    }

    private SingletonHungry() {

    }

    public static SingletonHungry getInstance() {

        return instance;

    }

    public static void main(String[] args) {

        System.out.println("使用饿汉模式加载实现单例,SingletonHungry.hashCode=" + instance.hashCode());

        for (int i = 0; i < 5; i++) {

            new Thread(() -> System.out.println("SingletonHungry.hashCode=" + SingletonHungry.getInstance().hashCode() + ",currentTime=" + System.currentTimeMillis())).start();

        }

    }

}

运行结果:

2、 使用DCL双检查锁机制(因最近在学习多线程技术,所以分别用synchronized和ReentrantReadWriteLock加锁做了测试)

A)、使用synchronized加锁

public class SingletonByDCLSync {

    private static SingletonByDCLSync instance = null;

    private SingletonByDCLSync() {

    }

    public static SingletonByDCLSync getInstance() {

//        try {

//            if (null == instance) {

//                synchronized (SingletonByDCLSync.class) {

//                    // 为了与ReentrantReadWriteLock效率做对比

//                    Thread.sleep(2000);

//                    if (null == instance) {

//                        instance = new SingletonByDCLSync();

//                    }

//                }

//            }

//        } catch (InterruptedException e) {

//            e.printStackTrace();

//        }

        syncInit();

        return instance;

    }

    // 同步的静态方法与类锁效果一样

    synchronized private static void syncInit() {

        try {

            if (null == instance) {

                instance = new SingletonByDCLSync();

            }

            // 为了与ReentrantReadWriteLock效率做对比

            Thread.sleep(2000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    public static void main(String[] args) {

        System.out.println("使用DCL双检查锁机制(synchronized)实现单例");

        for (int i = 0; i < 5; i++) {

            new Thread(() -> System.out.println("SingletonByDCLSync.hashCode=" + SingletonByDCLSync.getInstance().hashCode() + ",currentTime=" + System.currentTimeMillis())).start();

        }

    }

}

运行结果:

B)、使用ReentrantReadWriteLock加锁

public class SingletonByDCLLock {

    private static SingletonByDCLLock instance = null;

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private SingletonByDCLLock() {

    }

    public static SingletonByDCLLock getInstance() {

        if (null == instance) {

            try {

                // 使用读锁共享原理提高效率

                lock.readLock().lock();

                Thread.sleep(2000);

                if (null == instance) {

                    instance = new SingletonByDCLLock();

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            } finally {

                lock.readLock().unlock();

            }

        }

        return instance;

    }

    public static void main(String[] args) {

        System.out.println("使用DCL双检查锁机制(ReentrantReadWriteLock)实现单例");

        for (int i = 0; i < 5; i++) {

            new Thread(() -> System.out.println("SingletonByDCLLock.hashCode=" + SingletonByDCLLock.getInstance().hashCode() + ",currentTime=" + System.currentTimeMillis())).start();

        }

    }

}

运行结果:

从运行结果证明了ReentrantReadWriteLock读锁共享原理,大家也可以尝试一下写锁、 “读写锁”、“写读锁”的效果,这些的效果跟synchronized是一样的,因为它们都是互斥(同步)。

3、 使用静态内置类(推荐)

public class SingletonByInner implements Serializable {

    private static final long serialVersionUID = 2766178451076677731L;

    private static class Singleton {

        private static final SingletonByInner instance = new SingletonByInner();

    }

    private SingletonByInner() {

    }

    public static SingletonByInner getInstance() {

        return Singleton.instance;

    }

//    protected Object readResolve() {

//        System.out.println("调用了readResolve()方法");

//        return Singleton.instance;

//    }

    public static void main(String[] args) {

        for (int i = 0; i < 5; i++) {

            new Thread(() -> {

                // 序列化

                SingletonByInner inner = SingletonByInner.getInstance();

                try {

                    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("C:\temp\serializable.txt")));

                    oos.writeObject(inner);

                    oos.close();

                } catch (IOException e) {

                    e.printStackTrace();

                }

                // 反序列化

                try {

                    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("C:\temp\serializable.txt")));

                    SingletonByInner instance = (SingletonByInner) ois.readObject();

                    ois.close();

                    System.out.println("SingletonByInner.hashCode=" + instance.hashCode() + ",currentTime=" + System.currentTimeMillis());

                } catch (ClassNotFoundException e) {

                    e.printStackTrace();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }).start();

        }

    }

}

不调用readResolve()方法的情况下,运行结果:

将注释代码解开,调用readResolve()方法,运行结果:

注:之所以要调用readResolve()方法,是因为此类实现了序列化接口,进行了序列化操作,破坏了单例模式。其它实现方式如果进行了序列化操作,同样需要调用readResolve()方法。

4、 使用enum枚举数据类型(枚举enum和静态代码块的特性相似,构造方法会自动被调用)

public class SingletonByEnum {

    public enum SingleEnum {

        obj;

        private Object object;

        SingleEnum() {

            object = new Object();

        }

        public Object getObj() {

            return object;

        }

    }

    private SingletonByEnum() {

    }

    public static Object getInstance() {

        return SingleEnum.obj.getObj();

    }

    public static void main(String[] args) {

        System.out.println("使用enum枚举数据类型实现单例");

        for (int i = 0; i < 5; i++) {

            new Thread(() -> System.out.println("SingletonByEnum.hashCode=" + SingletonByEnum.getInstance().hashCode() + ",currentTime=" + System.currentTimeMillis())).start();

        }

    }

}

运行结果:

    千万不要试图去研究 研究了很久都整不明白的东西,或许是层次不到,境界未到,也或许是从未在实际的应用场景接触过,这种情况下去研究,只会事倍功半,徒劳一番罢了。能做的就是不断的沉淀知识,保持一颗积极向上的学习心态,相信终有一天所有的困难都会迎刃而解。
原文地址:https://www.cnblogs.com/54hsh/p/11214356.html