Java设计模式之单例模式

xiaoxiao2021-02-27  326

单例模式,也叫单子模式,是一种简单和常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理,有利于Java垃圾回收。

单例模式主要有3个特点:

1、单例类确保自己只有一个实例。2、单例类必须自己创建自己的实例。3、单例类必须为其他对象提供唯一的实例。

单例模式的实现方式有五种方法:懒汉,恶汉,双重校验锁,枚举和静态内部类。

懒汉模式

懒汉方式,指全局的单例实例在第一次被使用时构建。注意线程安全与否。 线程不安全:

//Non Thread Safe public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }

这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。 线程安全:

public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

恶汉模式

饿汉方式,指全局的单例实例在类装载时构建。

public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

静态内部类

因为单例是静态的final变量,当类第一次加载到内存中的时候就初始化了,其thread-safe性由 JVM 来负责保证。

//一个延迟实例化的内部类的单例模式 public final class Singleton { //一个内部类的容器,调用getInstance时,JVM加载这个类 private static final class SingletonHolder { static final Singleton singleton = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return SingletonHolder.singleton; } }

首先,其他类在引用这个Singleton的类时,只是新建了一个引用,并没有开辟一个的堆空间存放(对象所在的内存空间)。接着,当使用Singleton.getInstance()方法后,Java虚拟机(JVM)会加载SingletonHolder.class(JLS规定每个class对象只能被初始化一次),并实例化一个Singleton对象。

缺点:需要在Java的另外一个内存空间(Java PermGen 永久代内存,这块内存是虚拟机加载class文件存放的位置)占用一个大块的空间。

枚举

枚举单例(Enum Singleton)是实现单例模式的一种新方式,枚举这个特性是在Java5才出现的。《Effective Java》一书中有介绍这个特性,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

public enum Singleton { INSTANCE; public void whateverMethod() { } }

默认枚举实例的创建是线程安全的,但是在枚举中的其他任何方法由程序员自己负责。如果你正在使用实例方法,那么你需要确保线程安全(如果它影响到其他对象的状态的话)。传统单例存在的另外一个问题是一旦你实现了序列化接口,那么它们不再保持单例了,但是枚举单例,JVM对序列化有保证。枚举实现单例的好处:有序列化和线程安全的保证,代码简单。

双重校验锁

public class Singleton { public static final Singleton singleton = null; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronize (Singleton.class){ if( singleton == null ) { // double check singleton = new Singleton(); } } return singleton; } }

当两个线程执行完第一个 singleton == null 后等待锁, 其中一个线程获得锁并进入synchronize后,实例化了,然后退出释放锁,另外一个线程获得锁,进入又想实例化,会判断是否进行实例化了,如果存在,就不进行实例化了。

转载请注明原文地址: https://www.6miu.com/read-2513.html

最新回复(0)