单例模式如何在多线程下保证单例
单例模式的实现方式:
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(); } } }
运行结果:
千万不要试图去研究 研究了很久都整不明白的东西,或许是层次不到,境界未到,也或许是从未在实际的应用场景接触过,这种情况下去研究,只会事倍功半,徒劳一番罢了。能做的就是不断的沉淀知识,保持一颗积极向上的学习心态,相信终有一天所有的困难都会迎刃而解。