Java设计模式之单例模式Singleton Pattern详解

 更新时间:2023年12月04日 10:21:16   作者:制造bug的键盘钢琴师  
这篇文章主要介绍了Java设计模式之单例模式Singleton Pattern详解,一些常用的工具类、线程池、缓存,数据库,数据库连接池、账户登录系统、配置文件等程序中可能只允许我们创建一个对象,这就需要单例模式,需要的朋友可以参考下

1.单例设计模式的设计思想与应用场景

设计目的

避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。

设计思想

(1)不允许其他程序用new对象: 因为new就是开辟新的空间,在这里更改数据只是更改的所创建的对象的数据,如果可以new的话,每一次new都产生一个对象,这样肯定保证不了对象的唯一性。

(2)在该类中创建对象: 因为不允许其他程序new对象,所以这里的对象需要在本类中new出来

(3)对外提供一个可以让其他程序获取该对象的方法

应用场景

一些常用的工具类、线程池、缓存,数据库,数据库连接池、账户登录系统、配置文件等程序中可能只允许我们创建一个对象。

2.单例模式的写法

单例模式的写法大的方面可以分为5种五种

1.懒汉式

2.饿汉式

3.双重校验锁

4.静态内部类

5.枚举

2.1单例模式的饿汉式

public class Singleton {
		private static Singleton instance=new Singleton();
		//构造器私有化,本身不可创建
		private Singleton(){
		}
		//定义一个获取对象的方法
		public static Singleton getInstance(){
			 return instance;
		}
}

饿汉式的静态代码块写法

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

备注:两种写法的主要区别在于加载的时间点上,前者在于静态常量显示初始化之后,后者在与静态代码块加载运行的时候。

优点:饿汉式的写法主要的优势在于实现方式简单,在类加载的时候完成了对象的实例化,因此也可以有效避免线程问题。

缺点:可能造成资源浪费的情况,由于对象实例是在代码加载过程中进行的,只要代码成功加载对象实例就会被创建导致内存浪费。【被占用的内存是非常小的,所以也是推介可以使用的。】

2.2单例模式的懒汉式

2.2.1 懒汉式普通写法

线程不安全,不推荐使用

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

与饿汉式的写法相比较,懒汉式主要是在对**getInstance()**方法调用的时候进行实例化对象的操作,既用之再造,节省了内存,故因此称之为懒汉式。

懒汉式的普通写法式存在线程安全问题的,总的来说也就是存在重复创建对象的风险。假设线程1进来,通过if(instance==null)的条件判断准备运行”insatance=new Singleton()“这条代码前,又有线程2运行,此时还没有成功进行Singleton对象实例的创建,if 的条件判断仍然成立,线程2也会去执行instance=new Singleton()这条语句,这样就会导致多个Singleton对象被创建。这就是为什么懒汉式的普通写法会造成线程安全的原因。

2.2.2懒汉式同步代码写法

【给创建对象的语句加锁】与2.2.1的写法类似仍为线程不安全的写法,不推荐使用

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

与2.2.1相类似,synchronized (Singleton.class) 锁住的只是对于对象的创建,仍有有可能再线程1获得”钥匙“还未进行对象的创建的时候,线程二也通过条件判断进行锁等待,一旦获得“钥匙”也会进行对象创建,也是会导致多对象的创建。

2.2.3懒汉式同步方法写法

线程安全,但效率非常低,不推荐使用

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

缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步,方法进行同步效率太低。

2.2.4单例模式懒汉式双重校验锁

推介使用

public class Singleton {
	/**
	 * 懒汉式变种,属于懒汉式中最好的写法,保证了:延迟加载和线程安全
	 */
	private static Singleton instance=null;
	private Singleton() {};
	public static Singleton getInstance(){
		 if (instance == null) {  
	          synchronized (Singleton.class) {  
	              if (instance == null) {  
	            	  instance = new Singleton();  
	              }  
	          }  
	      }  
	      return instance;  
	}
}

通过双层if(insatance==null)的条件限制,防止了多线程运行就算在解锁条件下多对象的创建。

通过synchronized 保证了线程安全,但再第一次对象成功创建后,就可以通过return instance 避免了加锁代码的重复运行,大大提升了效率。

在线程安全以及效率之间找到了一个平衡点。

2.3内部类

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

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同

的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时

并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是

无法进入的。

优点:避免了线程不安全,延迟加载,效率高。

2.4枚举

public enum SingletonEnum {
	 instance; 
	 private SingletonEnum() {}
	 public void method(){
	 }
}

也就是直接将枚举类Enum直接当作一个上述例子中的Singleton所对应的实例去进行使用,可以直接通过SingletonEnum.instance.method();的方式对实例中我们要使用的方法进行调用。

不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过,这种方式也是最好的一种方式,如果在开发中JDK满足要求的情况下建议使用这种方式。

总结

对于写法的选择,开源项目也是采用的单例模式懒汉式双重校验锁这种写法,其实最安全的写法是枚举,它的实现非常简单而且最安全可谓很完美,但是可能是因为支持的JDK版本有限又或者是因为枚举大家不熟悉所以目前使用的人并不多,可以更多的进行尝试。

另外当我们使用反射机制时可能不能保证实例的唯一性,但是枚举始终可以保证唯一性【这与枚举类在初始类加载的过程以及进行的操作有关】。

到此这篇关于Java设计模式之单例模式Singleton Pattern详解的文章就介绍到这了,更多相关Java单例模式Singleton Pattern内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java类加载器和类反射使用示例

    java类加载器和类反射使用示例

    这篇文章主要介绍了java类加载器和类反射使用示例,需要的朋友可以参考下
    2014-03-03
  • springboot+redis 实现分布式限流令牌桶的示例代码

    springboot+redis 实现分布式限流令牌桶的示例代码

    这篇文章主要介绍了springboot+redis 实现分布式限流令牌桶 ,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Java流程控制break和continue

    Java流程控制break和continue

    这篇文章主要介绍了Java流程控制break和continue,下面文章围绕break和continue的相关资料展开详细内容,具有一定的参考价值,需要的小伙伴可以参考一下
    2021-12-12
  • Java实现经典游戏黄金矿工的示例代码

    Java实现经典游戏黄金矿工的示例代码

    《黄金矿工》游戏是一个经典的抓金子小游戏,它可以锻炼人的反应能力。本文将用Java实现这一经典的游戏,感兴趣的小伙伴可以了解一下
    2022-02-02
  • Java 实现判定顺序表中是否包含某个元素(思路详解)

    Java 实现判定顺序表中是否包含某个元素(思路详解)

    这篇文章主要介绍了Java 实现判定顺序表中是否包含某个元素,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • 详解java动态代理的2种实现方式

    详解java动态代理的2种实现方式

    目前Java开发包中包含了对动态代理的支持,但是其实现只支持对接口的的实现。这篇文章主要介绍了详解java动态代理的2种实现方式 ,有兴趣的可以了解一下。
    2016-11-11
  • MPAndroidChart开源图表库的使用介绍之饼状图、折线图和柱状图

    MPAndroidChart开源图表库的使用介绍之饼状图、折线图和柱状图

    这篇文章主要介绍了MPAndroidChart开源图表库的使用介绍之饼状图、折线图和柱状图的相关资料,需要的朋友可以参考下
    2016-02-02
  • Spring中自定义数据类型转换的方法详解

    Spring中自定义数据类型转换的方法详解

    Spring3引入了一个core.onvert包,提供一个通用类型转换系统。在Spring容器中,可以使用这个系统作为PropertyEditor实现的替代,将外部化的bean属性值字符串转换为所需的属性类型。本文将详解这一系统的使用方法,需要的可以参考一下
    2022-06-06
  • SpringBoot事务异步调用引发的bug解决

    SpringBoot事务异步调用引发的bug解决

    本文主要介绍了SpringBoot事务异步调用引发的bug解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • java 两个数组合并的几种方法

    java 两个数组合并的几种方法

    本篇文章主要介绍了java 两个数组合并的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08

最新评论