设计模式之单例模式

单例模式用于创建独一无二的对象实例,可分为懒汉式和饿汉式。懒汉式延迟加载,饿汉式一开始就实例化。但在多线程环境中,懒汉式会有问题。介绍了线程安全的单例实现方式,如synchronized、双重检查锁、内部类实现和枚举实现。使用单例模式可节约资源、减轻GC压力。

单例模式是日常开发中经常使用的模式之一,需要大家对它有足够的了解。尽管单例模式理解相对其它模式简单,但是在一些特殊情况下,实现单例模式也需要特别注意。

定义

用来创建独一无二的,只有一个对象实例的类。在实际项目中,单例模式使用非常频繁,例如:线程池、数据库连接池、缓存、日志对象、Spring默认Bean对象等。在这些情况下,若使用过多实例,可能会导致系统资源使用过量、数据不一致等问题。

大家可能会问,创建全局静态变量也能做到如此效果,为什么还会出现一个单例模式呢?没错,全局静态变量确实可以实现如此效果,可是全局变量是在程序启动时就已经创建好对象了,如果该对象占用极大内存并且使用相对较少,就会造成内存资源浪费。使用单例模式,我们可以在需要的时候才去创建对象并且只实例化一次。

实现

单例模式根据实现方式可以分为两种:懒汉式和饿汉式。

懒汉式,顾名思义就是使用的时候才会进行实例化,延迟加载。代码如下:

public class Singleton {
		private static Singleton INSTANCE;
	
		private Singleton(){
			if (INSTANCE != null) {
				throw new RuntimeException(this.getClass() + "不具有该Constructor方法");	
			}
		}
		public static Singleton newInstance() {
                  if(INSTANCE==null){
                       INSTANCE = new Singleton();
                   }
			return INSTANCE;
		}
}

使用private修饰构造方法,为了防止该类被实例化。另外构造函数中判断INSTANCE是否为空,是为了防止使用反射强行执行构造函数实例化,违反单例结果。

饿汉式,顾名思义就是一开始就进行实例化。代码如下:

   public class Singleton {
		private static final Singleton INSTANCE = new Singleton();
		private Singleton() {
			if (INSTANCE != null) {
				throw new RuntimeException(this.getClass() + "不具有该Constructor方法");	
			}
		}
		public static Singleton newInstance() {
			return INSTANCE;
		}
    }

大家是不是觉得单例模式特别简单,其实上述两种实现方式还是有点问题。以懒汉式来说,newInstance()方法,其中我们判断INSTANCE是否为空,不为空则进行实例化,这段代码在单线程中运行确实没有什么问题,但是在多线程环境中,假设现在有两个线程同时判空,那么两个线程都会进行实例化,这个时候,就是有问题的。下面我们就来简单介绍一下,多线程环境中如何实现单例模式。

线程安全的单例

饿汉式是在JVM加载该类的时候就会进行实例化,所以它是线程安全的。上述实现的懒汉式代码,如何解决才能实现线程安全的呢?大家首先想到的应该是加锁,没错,最简单的实现方式是这样的:

synchronized

public static synchronized Singleton newInstance() {
        if(INSTANCE==null){
            INSTANCE = new Singleton();
         }
         return INSTANCE;
}

采用synchronized同步关键字,能够确保多线程执行的时候,只有一个线程能够真正的进行实例化。这种方式简单是简单,可是有不好的地方,当我们第一次调用的时候,确实需要同步,但是在以后调用的时候,多个线程每次只能有一个线程获得实例,这性能肯定是不好的。

双重检查锁

上述我们知道,单纯的加锁会引起性能问题。现在我们思考一下,有没有一种方式,只会在第一次的时候进行加锁,以后都不会涉及同步。双重检查锁就是这样,先判断对象是否为空,再进行加锁进行实例化,这样只会在第一次的时候是同步操作,以后都不会经过锁操作,避免出现性能问题。代码如下:

public static Singleton newInstance() {
        if(INSTANCE==null){
            synchronized(Singleton.class){
                if(INSTANCE==null){
                     INSTANCE = new Singleton();
                }
            }
         }
         return INSTANCE;
}

大家请注意,使用双重检查锁的时候,一定要在申明变量的时候加上volatile关键字,例如:

private volatile static Singleton singleton;

这是为什么呢?在INSTANCE = new Singleton();这一行代码中,分为三个步骤:1.分配内存空间。2.初始化对象。3.将对象指向分配的内存空间。关于这三个步骤,由于2和3两个步骤之间没有太大关联,所以编译器在执行的时候,可能会进行指令重排,导致2和3执行顺序颠倒,引起另外一个线程读取到还未进行初始化的对象。为了解决这个问题,就必须使用volatile,它会防止指令重排。

上述都是采用加锁的方式实现线程安全,有没有不使用加锁的方式呢?肯定有,比如下面我们使用的这两种方式,

内部类实现

public class Singleton {
		private Singleton() {}
		public static Singleton newInstance() {
			return SingletonHolder.instance;
		}
             private static class SingletonHolder{
                 private static Singleton instance=new Singleton();
             }
}

这种方式不仅是线程安全的,并且是延迟加载的,只有在第一次调用newInstance()时,才会创建Singleton实例。这里主要应用了内部类和类的初始化方式,内部类被申明为private,外部类不能对它进行初始化,只能由newInstance()方法进行初始化。这种方式没有涉及到锁操作,所以在高并发的情况下性能优越。

枚举实现

枚举的特性应该不用介绍吧,天生就是单例的,所以使用枚举方式,性能也是非常优越的。

public enum Singleton {
		INSTANCE();
		public void print(){
			System.out.println("我只是一个单实例:"+this);
		}
}

总结

单例模式分为懒汉式和饿汉式。对于频繁使用的对象,特别是一些占内存较大的对象,使用单例模式可以节约资源,并且由于new操作的次数减少,可以减轻GC的压力。在多线程的环境下,要注意单例模式的线程安全。

【博士论文复现】【阻抗建模、验证扫频法】光伏并网逆变器扫频与稳定性分析(包含锁相环电流环)(Simulink仿真实现)内容概要:本文档是一份关于“光伏并网逆变器扫频与稳定性分析”的Simulink仿真实现资源,重点复现博士论文中的阻抗建模与扫频法验证过程,涵盖锁相环和电流环等关键控制环节。通过构建详细的逆变器模型,采用小信号扰动方法进行频域扫描,获取系统输出阻抗特性,并结合奈奎斯特稳定判据分析并网系统的稳定性,帮助深入理解光伏发电系统在弱电网条件下的动态行为与失稳机理。; 适合人群:具备电力电子、自动控制理论基础,熟悉Simulink仿真环境,从事新能源发电、微电网或电力系统稳定性研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①掌握光伏并网逆变器的阻抗建模方法;②学习基于扫频法的系统稳定性分析流程;③复现高水平学术论文中的关键技术环节,支撑科研项目或学位论文工作;④为实际工程中并网逆变器的稳定性问题提供仿真分析手段。; 阅读建议:建议读者结合相关理论教材与原始论文,逐步运行并调试提供的Simulink模型,重点关注锁相环与电流控制器参数对系统阻抗特性的影响,通过改变电网强度等条件观察系统稳定性变化,深化对阻抗分析法的理解与应用能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值