原文连接:https://liushiming.cn/2020/03/01/java-design-pattern-singleton/html
概述
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责建立本身的对象,同时确保只有单个对象被建立。这个类提供了一种访问其惟一的对象的方式,能够直接访问,不须要实例化该类的对象。java
特色:设计模式
- 单例类只能有一个实例。
- 单例类必须本身建立本身的惟一实例。
- 单例类必须给全部其余对象提供这一实例。
线程不安全单例
饿汉模式
优势:安全
- 实现简单
- 线程安全
缺点:多线程
- 可能形成资源浪费,即便不使用也会占用内存,特别是实例比较大的时候
适用场景: 只有在初始化类的成本较低或程序老是须要类的实例时才使用并发
public final class EagerSingleton { private static EagerSingleton singObj = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getSingleInstance() { return singObj; } }
懒汉模式
优势:ide
- 对象仅在须要时被建立,无内存和cpu的浪费
缺点:高并发
- 非线程安全,多个线程同时走到
if (null == singObj )
就会建立多个实例
适用场景: 非多线程环境测试
public final class LazySingleton { private static LazySingleton singObj = null; private LazySingleton() { } public static LazySingleton getSingleInstance() { if (null == singObj ) { singObj = new LazySingleton(); } return singObj; } }
线程安全单例
线程安全单例测试方法,新建多个线程同时实例化单例类,看hashCode是否一致:ui
class MyThread extends Thread { @Override public void run() { System.out.println(Singleton.getInstance().hashCode()); } public static void main(String[] args) { MyThread[] mts = new MyThread[10]; for (int i = 0; i < mts.length; i++) { mts[i] = new MyThread(); } for (int j = 0; j < mts.length; j++) { mts[j].start(); } } }
同步方法
优势:
- 对象仅在须要时被建立,无内存和cpu的浪费
- 线程安全,由于给方法加了
Synchronized
同步锁
缺点:
- 方法上加锁大幅限制了多线程的效率
适用场景: 不建议使用
public final class ThreadSafeSingleton { private static ThreadSafeSingleton singObj = null; private ThreadSafeSingleton() { } public static synchronized ThreadSafeSingleton getInstance() { if (null == singObj) { singObj = new ThreadSafeSingleton(); } return singObj; } }
同步代码块、双检查
优势:
- 对象仅在须要时被建立,无内存和cpu的浪费
- 线程安全。由于给须要加锁的代码块加了
Synchronized
同步锁,且代码块中有双检查
缺点:
- 代码略繁琐
适用场景: 基本上都适用,是一种较优的单例模式实现
class DoubleCheckedSingleton { private static DoubleCheckedSingleton singObj = null; private DoubleCheckedSingleton() { } public static DoubleCheckedSingleton getInstance() { if (null == singObj) { synchronized (DoubleCheckedSingleton.class) { if (null == singObj) { singObj = new DoubleCheckedSingleton(); } } } return singObj; } }
内部类
优势:
- 对象仅在须要时被建立,无内存和cpu的浪费
- 线程安全
- 代码简洁
缺点:
- 静态内部类虽然保证了单例在多线程并发下的线程安全性,可是在遇到序列化对象时,默认的方式运行获得的结果是多例的。
适用场景: 不涉及序列化与反序列化的场景
public class Singleton { private static class SingletonHolder { public final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
静态代码块
优势:
- 静态代码块在使用类的时候被执行一次 ,且仅执行一次,实现了延迟实例化
- 线程安全
缺点:
- 静态内部类虽然保证了单例在多线程并发下的线程安全性,可是在遇到序列化对象时,默认的方式运行获得的结果是多例的。
适用场景: 推荐
public class MySingleton{ private static MySingleton instance = null; private MySingleton(){} static{ instance = new MySingleton(); } public static MySingleton getInstance() { return instance; } }
序列化与反序列化单例问题
在不涉及序列化与反序列化的场景中,以上线程安全的单例类实现都没有问题,可是在反序列化时,默认获得的结果是多例的
单例实现:
public class Singleton { private static class SingletonHolder { public final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
测试代码:
public class SingletonTest { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); File file = new File("MySingleton.txt"); try { FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(singleton); fos.close(); oos.close(); System.out.println(singleton.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); Singleton rSingleton = (Singleton) ois.readObject(); fis.close(); ois.close(); System.out.println(rSingleton.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
结果:
1023892928 193064360
从结果中咱们发现,序列号对象的hashCode和反序列化后获得的对象的hashCode值不同,说明反序列化后返回的对象是从新实例化的,单例被破坏了。那怎么来解决这一问题呢?
解决办法就是在反序列化的过程当中使用readResolve()方法,单例实现的代码以下:
class Singleton implements Serializable { private static final long serialVersionUID = 1L; private static class SingletonHolder { public final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } protected Object readResolve() throws ObjectStreamException { System.out.println("调用了readResolve方法!"); return getInstance(); } }
结果:
1023892928 调用了readResolve方法! 1023892928
对于Serializable和Externalizable类,readResolve方法容许类在将从流中读取的对象返回给调用者以前替换/解析它。经过实现readResolve方法,一个类能够直接控制被反序列化的自身实例的类型和实例。