单例模式

单例模式(Singleton Pattern

意图

确保一个类只有一个实例,并提供一个全局访问点。

单例模式的三个要点:1.单例类只有一个实例对象;2.该单例对象必须由单例类自行创建;3.单例类对外提供一个访问该单例的全局访问点。

动机

对于系统中的某些类来说,只能有一个对象,例如:线程池、缓存、注册表的对象等。若制造出多个实例,就会导致许多问题的产生,如:程序行为异常、资源使用过度、结果不一致等。
如何保证一个对象只能被实例化一次?
利用静态变量、静态方法和适当的访问修饰符,可以实现。而更好的方法是让单例类自身负责管理它的唯一实例。

适用性

单例常常用来管理共享的资源,例如:Windows 的回收站、操作系统中的文件系统、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框等常常被设计成单例。

结构


实现

  • 饿汉式单例
public class Singleton {
    // 私有的静态属性
    private static Singleton instance = new Singleton();

    // 私有的构造方法
    private Singleton() {
    }

    // 公共的静态方法,实例的全局访问点
    public static Singleton getInstance() {
        return instance;
    }

}

类一旦加载就创建一个实例,保证在调用 getInstance 方法之前实例已经存在了。线程不安全问题主要是由于 instance 被实例化多次,采取直接实例化 instance 的方式就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。

  • 懒汉式单例(非线程安全)
public class Singleton {
    //私有静态变量 instance 被延迟实例化
    private static Singleton instance = null;

    private Singleton() {}

    public static Singleton getInstance() {
        // 如果多个线程能够同时进入,将导致实例化多次 instance
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

私有静态变量 instance 被延迟实例化,他好处是,如果没有用到该类,那么就不会实例化,从而节约资源。但是这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (instance == null) ,并且此时 instance 为 null,那么会有多个线程执行 instance = new Singleton();

  • 双重校验锁机制(线程安全)
public class Singleton {
    // volatile修饰共享变量,保证了不同线程对变量进行操作时的可见性,可以禁止 JVM 的指令重排序
    private static volatile Singleton instance = null;

    private Singleton() {}

    public static Singleton getUniqueInstance() {
        // 第一次检查,用来避免 instance 已经被实例化也去做同步操作
        if (instance != null) {
            // 不创建
        } else {
            synchronized (Singleton.class) {
            // 第二次检查,加锁,确保只有一个线程进行实例化操作
            if (instance == null) {
                instance = new Singleton();
                }
            }
        }	
        return instance;
    }

}

双重校验锁(Double Check Locking,简称DCL),资源利用率高,第一次执行getInstance时单例对象才被实例化;但是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL虽然在一定程度解决了资源的消耗、多余的同步和线程安全等问题,但是他还是在某些情况会出现失效的问题,也就是DCL失效。

  • 静态内部类单例模式(线程安全)
public class Singleton { 
    // 静态内部类,只在第一次使用时进行类加载
    private static class MySingletonFactory {  
        // 使用final修饰,可以避免变量被重新赋值,JVM也不用去跟踪该引用是否被更改
        private static final Singleton INSTANCE = new Singleton();
    } 

    private Singleton(){}

    public static Singleton getInstance(){  
        return MySingletonFactory.INSTANCE;  
    }       
} 

静态内部类,只在第一次使用时进行类加载,也就是说当调用 getInstance() 从而触发 MySingletonFactory.INSTANCE 从而触发时 MySingletonFactory 才会被加载,此时初始化 INSTANCE 实例,且 JVM 能确保 INSTANCE 只被实例化一次。(不仅可以延迟实例化,而且JVM 提供了对线程安全的支持)。

  • 序列化与反序列化实现单例模式(线程安全)
public class Singleton implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class MySingletonFactory {
        private static final SerializeSingleton instance = new Singleton();
    }

    private Singleton() {}

    public static SerializeSingleton getInstance() {
        return MySingletonFactory.instance;
    }
    // 不加readResolve(),默认的方式运行得到的结果就是多例的
    protected Object readResolve() throws ObjectStreamException {
        System.out.println("调用了readResolve方法!");
            return MySingletonFactory.instance;
    }
}

// 测试伪代码
public class Test{  
    public static void main(String[] args) throws {  
        Singleton singleton = Singleton.getInstance();    
        File file = new File("MySingleton.txt");    
        // 序列化
        FileOutputStream fos = new FileOutputStream(file); 
        ObjectOutputStream oos = new ObjectOutputStream(fos);  
        oos.writeObject(singleton);
        System.out.println("序列化 hashCode: "+singleton.hashCode());  
        
        // 反序列化  
        FileInputStream fis = new FileInputStream(file);  
        ObjectInputStream ois = new ObjectInputStream(fis);
        Singleton rSingleton = (Singleton) ois.readObject();
        System.out.println("反序列化 hashCode: "+rSingleton.hashCode());  

        // 资源释放        
    }  
} 

在反序列化时,ObjectInputStreamreadObject() 的内部代码执行顺序:readObject() --> readObject0() --> readOrdinaryObject() --> invokeReadResolve() --> readResolveMethod.invoke()

invokeReadResolve() 中 使用 readResolveMethod.invoke() 克隆对象。换句话说,invokeReadResolve() 使用反射机制创建新的对象,从而破坏了单例唯一性。

readResolve() 会在 ObjectInputStream 会检查对象的 class 是否定义了 readResolve()。如果定义了,将由 readResolve() 指定返回的对象。返回对象的类型一定要是兼容的,否则会抛出 ClassCastException

  • 枚举实现单例模式(线程安全)
public enum EnumSingleton implements SingletonInterface {
	INSTANCE { 
        @Override
        public void doSomething() {
            System.out.println("EnumSingleton singleton");
        }
    };

    public static EnumSingleton getInstance() {
        return EnumSingleton.INSTANCE;
    }
}

public interface SingletonInterface {	
	void doSomething();
}

已知应用

  • java.lang.Runtime
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}

    // ......
}

    @Test
    public void testRuntime() {
        Runtime r1 = Runtime.getRuntime();  
        Runtime r2 = Runtime.getRuntime();  
        //“==”为地址判断,为true表示:r1与r2指向同一对象。  
        System.out.println(r1 == r2);	// 输出 true
    }

以上为 java.lang.Runtime 类的部分代码,是饿汉式单例模式,在该类第一次被 classloader 的时候创建唯一实例。

Runtime 类封装了 Java 运行时的环境。每一个 Java 程序实际上都是启动了一个 JVM 进程,每个 Java 程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。由于Java 是单进程的,所以,在一个JVM 中,Runtime 的实例应该只有一个。

  • Spring 依赖注入 Bean 实例(默认单例)

相关模式

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值