创造模式-单例模式

概述

单例模式(Singleton Pattern):确保一个类只有一个实例,而且自行实例化后向整个系统提供这个实例,这个类是单例类,它提供全局的该单一实例的访问方式。单例模式是创建类的唯一实例的创造模式。

问题

怎样确保一个特殊类的示例是独一无二的(唯一的实例),并且这个实例易于访问?

解决方案

  1. 全局变量
    提供对象向的引用。
  2. 私有的构造器
    防止构造器在其他类被调用创造多个实例。
  3. 共有的静态方法
    向系统内其他的类提供该引用,如果是全局变量是public的就不需要这个方法了。

结构

singletonPattern.png

单例模式有三个要点:

  • 一个类只能有一个实例
  • 它必须自行创建这个实例
  • 它必须自行向整个系统提供这个实例

java中的单例模式

经典单例模式
共有静态成员实现单例
/**
 * 共有静态成员实现单例
 * 
不需要提供访问成员变量的静态方法 * @author c_zhouwenjun-001 * */ public class PublicSingleton { /** * 共有的成员变量,在类初始化的时候调用构造方法,创建类的唯一实例 */ public static final PublicSingleton instance = new PublicSingleton(); private PublicSingleton() { } }
  • 特点:不需要提供访问成员变量的静态方法
饿汉模式
/**
 * 单例模式--饿汉模式
 * 
优点:线程安全 *
缺点:在类加载的时候加载,浪费内存 * @author c_zhouwenjun-001 * */ public class EagerSingleton { private static final EagerSingleton INSTANCE = new EagerSingleton(); private EagerSingleton() { } /** * 共有的静态成员变量访问方法 * @return */ public static EagerSingleton getInstance() { return INSTANCE; } }
  • 优点:线程安全,没有加锁,执行效率会提高。
  • 缺点:在类加载的时候加载,浪费内存
懒汉模式
**
 * 单例模式--懒汉模式
 * 
非线程安全 * */ public class LazySingleton { /** * 静态的私有的成员变量 */ private static LazySingleton instance = null; /** * 私有的构造器 */ private LazySingleton() { } /** * 共有静态的单例对象访问方法 * @return */ public static LazySingleton getInstance() { if(instance == null) { instance = new LazySingleton(); } return instance; } }
  • 优点:可以实现懒加载,防止内存浪费
  • 缺点: 非线程安全
线程安全懒汉模式
/**
 * 安全的懒加载单例模式
 * 
优点:保证了线程安全 *
缺点:效率低下每次调用getInstance方法,都需要在synchronize地方排队, * 实际遇到需要new对象的地方很少 * */ public class SafeLazySingleton { private static SafeLazySingleton instance = null; private SafeLazySingleton() { } /** * 使用synchronized关键字对SafeLazySingleton.class对象加锁 * @return */ public static synchronized SafeLazySingleton getIntance() { if (instance == null) { instance = new SafeLazySingleton(); } return instance; } }
  • 优点:使用了synchronize机制保证了线程安全
  • 缺点:效率低下每次调用getInstance方法,都需要在synchronize地方排队,实际遇到需要new对象的地方很少
双重锁检查机制
/**
 * 高效的线程安全的懒加载的单例模式
 *
 */
public class EffectiveSafeLazySingleton implements Serializable{

    private static final long serialVersionUID = 1L;
    /**
     * 使用volatile关键字 jVM提供的轻量级同步机制
     * 
1.保证静态成员变量instance的内存可见性; *
2.禁止指令重排优化 */ private static volatile EffectiveSafeLazySingleton instance = null; private EffectiveSafeLazySingleton() { } /** * 双重检查锁定 * @return */ public static EffectiveSafeLazySingleton getInstance() { if(instance == null) {//第一次判空操作 synchronized (EffectiveSafeLazySingleton.class) { if(instance == null) { //这一步,不是原子性操作,可能发生指令重排 //可能的指令为: 1.分配内存空间 // 2.初始化对象 // 3.将instance指向刚分配的内存空间 //指令重排后可能的执行顺序为,先执行1,3,2 //将会发生 线程A执行到1,3 //线程B 执行第一次判空操作 //对象非空 返回一个没有创建的对象 instance = new EffectiveSafeLazySingleton(); } } } return instance; } }
  • 优点:相对于synchronize实现对整个方法枷锁,减少了加锁的次数,性能提高
  • 缺点:实现复杂
静态内部类实现单例
/**
 * 使用内部类初始化单例对象
 *
 */
public class HolderSingleton {
    
    private HolderSingleton() {
        
    }
    private static class Holder{
        private static final HolderSingleton  HOLDER = new HolderSingleton();
    }
    /**
     * 使用内部类在内部类加载的时候初始化对象
     * 
类加载的最后一步,会执行类的方法初始化类, * 虚拟机会保证类的方法会被正确的枷锁、同步 * @return */ public static HolderSingleton getInstance() { return Holder.HOLDER; } }
  • 优点:利用内部类实现了线程安全的懒加载
单例模式破坏与保护
上述的单例模式的缺点
  • 都需要额外的工作来实现序列化安全,否则每次反序列化都会生成新的对象
    1.实现序列化接口
    2.所有实例成员变量都时瞬时(transient)
    3.加入readRsolve()方法
  • 可以使用反射机制强制调用私有的构造方法,创建新的对象
破坏单例模式
/**
 * java破坏单例模式
 * 
java实现的单例模式容易被反射和序列化破坏 * */ public class BrokenSingleton { public static void main(String[] args) throws Exception { // reflectBrokenSingleton(); // serializableBrokenSingleton(); preventReflectBrokenSingleton(); } /** * 使用反射机制破坏单例模式 * 可以使用计数的方式防止被反射模式破坏 * @throws Exception */ public static void reflectBrokenSingleton() throws Exception { EffectiveSafeLazySingleton s1 = EffectiveSafeLazySingleton.getInstance(); Constructor constructor = EffectiveSafeLazySingleton.class.getDeclaredConstructor(); //设置私有的构造器可以被访问 constructor.setAccessible(true); //使用构造器创建对象 EffectiveSafeLazySingleton s2 = constructor.newInstance(); System.out.println("s1:"+s1.hashCode()); System.out.println("s2:"+s2.hashCode()); //结果 //s1:366712642 //s2:1829164700 } /** * 使用序列化机制破坏单例模式 */ public static void serializableBrokenSingleton(){ EffectiveSafeLazySingleton s1 = EffectiveSafeLazySingleton.getInstance(); ByteArrayOutputStream baos = null; ByteArrayInputStream bais = null; ObjectOutputStream oos = null; ObjectInputStream ois = null; EffectiveSafeLazySingleton s2 = null; try { baos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(baos); oos.writeObject(s1); bais = new ByteArrayInputStream(baos.toByteArray()); ois = new ObjectInputStream(bais); s2 = (EffectiveSafeLazySingleton) ois.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { baos = null; bais = null; try { if(oos != null) oos.close(); if(ois != null) ois.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("s1:"+s1.hashCode()); System.out.println("s2:"+s2.hashCode()); //结果 //s1:366712642 //s2:1028566121 } /** * 防止反射破坏单例 * @throws Exception */ public static void preventReflectBrokenSingleton() throws Exception { PreventBrokenSingleton s1 = PreventBrokenSingleton.getInstance(); Constructor constructor = PreventBrokenSingleton.class.getDeclaredConstructor(); //设置私有的构造器可以被访问 constructor.setAccessible(true); //使用构造器创建对象 PreventBrokenSingleton s2 = constructor.newInstance(); System.out.println("s1:"+s1.hashCode()); System.out.println("s2:"+s2.hashCode()); //结果 // Exception in thread "main" java.lang.reflect.InvocationTargetException // at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) // at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) // at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) // at java.lang.reflect.Constructor.newInstance(Constructor.java:423) // at xin.chunfy.spria.util.designPattern.creationalPattern.singletonPattern.BrokenSingleton.preventReflectBrokenSingleton(BrokenSingleton.java:88) // at xin.chunfy.spria.util.designPattern.creationalPattern.singletonPattern.BrokenSingleton.main(BrokenSingleton.java:21) // Caused by: java.lang.RuntimeException: 单例对象已经被创造 // at xin.chunfy.spria.util.designPattern.creationalPattern.singletonPattern.PreventBrokenSingleton.(PreventBrokenSingleton.java:11) // ... 6 more } }
保护单例模式
public class PreventBrokenSingleton {
    /**
     * 计下实例被创造次数
     */
    private static int count = 0;
    private static volatile PreventBrokenSingleton instance = null;
    private PreventBrokenSingleton() {
        if(count > 0) {
            throw new RuntimeException("单例对象已经被创造");
        }
        count ++;
    };
    public static PreventBrokenSingleton getInstance() {
        if(instance == null) {
            synchronized (PreventBrokenSingleton.class) {
                if (instance == null) {
                    instance = new PreventBrokenSingleton();
                }
            }
        }
        return instance;
    }
    //TODO 防止单例对象被反射机制反复创建
}
  • 使用实例计数器防止构造器被多次调用
枚举单例模式
/**
 * 单例模式--枚举实现单例模式
 * 
《Effective Java》单元素的枚举类型已经成为实现Singleton的最佳方法。 *
优点 *
    *
  • 1.保证线程安全
  • *
  • 2.防止反射强行调用私有构造器
  • *
  • 3.提供了自动序列化,防止反序列化重新创建对象
  • *
      * @author c_zhouwenjun-001 * */ public enum EnmuSingleton { INSTANCE; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
  • 《Effective Java》书中 :

单元素的枚举类型已经成为实现Singleton的最佳方法。

总结

优点:

  • 单例模式提供了对唯一实例的受控访问,节省了系统资源。

缺点:

  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。单例类即充当了工厂角色, 提供了工厂方法,又充当了产品角色。

你可能感兴趣的:(创造模式-单例模式)