前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java单例模式

Java单例模式

作者头像
用户4919348
发布2019-04-02 11:26:27
1K0
发布2019-04-02 11:26:27
举报
文章被收录于专栏:波波烤鸭波波烤鸭

  单例模式应该是我们接触的众多设计模式中的第一个,但是对于单例模式的一些细节地方对于初学者来说并不是很清楚,所以本文就来整理下单例模式。

单例模式

  单例模式的核心是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。

单例的使用场景

  1. Spring中bean对象的模式实现方式
  2. servlet中每个servlet的实例
  3. spring mvc和struts1框架中,控制器对象是单例模式
  4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
  5. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统
  6. 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
  7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源

单例模式优点

  1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
  2. 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理

单例的实现方式

实现方式

优缺点

饿汉式

线程安全,调用效率高 ,但是不能延迟加载

懒汉式

线程安全,调用效率不高,能延迟加载

双重检测锁式

由于JVM底层内部模型原因,偶尔会出问题。不建议使用

静态内部类式

线程安全,资源利用率高,可以延时加载

枚举单例

线程安全,调用效率高,但是不能延迟加载

饿汉式

  也就是类加载的时候立即实例化对象,实现的步骤是先私有化构造方法,对外提供唯一的静态入口方法,实现如下

代码语言:javascript
复制
/**
 * 单例模式:饿汉式
 * @author 波波烤鸭
 *
 */
public class SingletonInstance1 {
	// 声明此类型的变量,并实例化,当该类被加载的时候就完成了实例化并保存在了内存中
	private static SingletonInstance1 instance = new SingletonInstance1();

	// 私有化所有的构造方法,防止直接通过new关键字实例化
	private SingletonInstance1(){}
	// 对外提供一个获取实例的静态方法
	public static SingletonInstance1 getInstance(){
		return instance;
	}
}

  饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字

问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!

懒汉式

代码语言:javascript
复制
/**
 * 单例模式:懒汉式
 * @author 波波烤鸭
 *
 */
public class SingletonInstance2 {
	// 声明此类型的变量,但没有实例化
	private static SingletonInstance2 instance = null;

	// 私有化所有的构造方法,防止直接通过new关键字实例化
	private SingletonInstance2(){}
	// 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字
	public static synchronized SingletonInstance2 getInstance(){
		if(instance == null){
			// 当instance不为空的时候才实例化
			instance = new SingletonInstance2();
		}
		return instance;
	}
}

  此种方式在类加载后如果我们一直没有调用getInstance方法,那么就不会实例化对象。实现了延迟加载,但是因为在方法上添加了synchronized关键字,每次调用getInstance方法都会同步,所以对性能的影响比较大。

双重检测锁式

代码语言:javascript
复制
/**
 * 单例模式:懒汉式
 * 双重检测机制
 * @author 波波烤鸭
 *
 */
public class SingletonInstance3 {
	// 声明此类型的变量,但没有实例化
	private static SingletonInstance3 instance = null;

	// 私有化所有的构造方法,防止直接通过new关键字实例化
	private SingletonInstance3(){}
	// 对外提供一个获取实例的静态方法,
	public static  SingletonInstance3 getInstance(){
		if(instance == null){
			SingletonInstance3 s3 = null;
			synchronized(SingletonInstance3.class){
				s3 = instance;
				if(s3 == null){
					synchronized(SingletonInstance3.class){
						if(s3 == null){
							s3 = new SingletonInstance3();
						}
					}
				}
				instance = s3;
			}
		}
		return instance;
	}
}

  这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。 问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。

静态内部类式

代码语言:javascript
复制
/**
 * 静态内部类实现方式
 * @author 波波烤鸭
 *
 */
public class SingletonInstance4 {
	// 静态内部类
	public static class SingletonClassInstance{
		// 声明外部类型的静态常量
		public static final SingletonInstance4 instance = new SingletonInstance4();
	}
	// 私有化构造方法
	private SingletonInstance4(){}
	
	// 对外提供的唯一获取实例的方法
	public static SingletonInstance4 getInstance(){
		return SingletonClassInstance.instance;
	}
}

注意点:

  1. 外部类没有static属性,则不会像饿汉式那样立即加载对象。
  2. 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
  3. 兼备了并发高效调用和延迟加载的优势!– 兼备了并发高效调用和延迟加载的优势!

枚举单例

代码语言:javascript
复制
/**
 * 单例模式:枚举方式实现
 * @author dengp
 *
 */
public enum SingletonInstance5 {

	// 定义一个枚举元素,则这个元素就代表了SingletonInstance5的实例
	INSTANCE;
	
	public void singletonOperation(){
		// 功能处理
	}
}

测试代码

代码语言:javascript
复制
public static void main(String[] args) {
	SingletonInstance5 s1  = SingletonInstance5.INSTANCE;
	SingletonInstance5 s2  = SingletonInstance5.INSTANCE;
	System.out.println(s1 == s2); // 输出的是 true
}

优点

  1. 实现简单
  2. 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!

缺点

  1. 无延迟加载

单例模式漏洞

  1. 通过反射的方式我们依然可用获取多个实例(除了枚举的方式)
代码语言:javascript
复制
public static void main(String[] args) throws Exception, IllegalAccessException {
	SingletonInstance1 s1 = SingletonInstance1.getInstance();
	// 反射方式获取实例
	Class c1 = SingletonInstance1.class;
	Constructor constructor = c1.getDeclaredConstructor(null);
	constructor.setAccessible(true);
	SingletonInstance1 s2 = (SingletonInstance1)constructor.newInstance(null);
	System.out.println(s1);
	System.out.println(s2);
}

输出结果:

代码语言:javascript
复制
com.dpb.single.SingletonInstance1@15db9742
com.dpb.single.SingletonInstance1@6d06d69c

产生了两个对象,和单例的设计初衷违背了。 解决的方式是在无参构造方法中手动抛出异常控制

代码语言:javascript
复制
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance2(){
	if(instance != null){
		// 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化
		throw new RuntimeException("单例模式只能创建一个对象");
	}
}
在这里插入图片描述
在这里插入图片描述
  1. 通过反序列化的方式也可以破解上面几种方式(除了枚举的方式)
代码语言:javascript
复制
public static void main(String[] args) throws Exception, IllegalAccessException {
	SingletonInstance2 s1 = SingletonInstance2.getInstance();
	// 将实例对象序列化到文件中
	ObjectOutputStream oos = new ObjectOutputStream(
			new FileOutputStream("c:/tools/a.txt"));
	oos.writeObject(s1);
	oos.flush();
	oos.close();
	// 将实例从文件中反序列化出来
	ObjectInputStream ois = new ObjectInputStream(
			new FileInputStream("c:/tools/a.txt"));
	SingletonInstance2 s2 = (SingletonInstance2) ois.readObject();
	ois.close();
	System.out.println(s1);
	System.out.println(s2);
}

输出结果:

代码语言:javascript
复制
com.dpb.single.SingletonInstance2@5c647e05
com.dpb.single.SingletonInstance2@4c873330

是两个不同的对象,同样破坏了单例模式,这种情况怎么解决呢 我们只需要在单例类中重写readResolve方法并在该方法中返回单例对象即可,如下:

代码语言:javascript
复制
package com.dpb.single;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 单例模式:懒汉式
 * @author 波波烤鸭
 *
 */
public class SingletonInstance2 implements Serializable{
	
	// 声明此类型的变量,但没有实例化
	private static SingletonInstance2 instance = null;

	// 私有化所有的构造方法,防止直接通过new关键字实例化
	private SingletonInstance2(){
		if(instance != null){
			// 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化
			throw new RuntimeException("单例模式只能创建一个对象");
		}
	}
	// 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字
	public static synchronized SingletonInstance2 getInstance(){
		if(instance == null){
			// 当instance不为空的时候才实例化
			instance = new SingletonInstance2();
		}
		return instance;
	}
	// 重写该方法,防止序列化和反序列化获取实例
	private Object readResolve() throws ObjectStreamException{
		return instance;
	}
}
在这里插入图片描述
在这里插入图片描述

说明:readResolve方法是基于回调的,反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!

常见的五种单例模式在多线程环境下的效率测试

大家只要关注相对值即可。在不同的环境下不同的程序测得值完全不一样

在这里插入图片描述
在这里插入图片描述

总结

这几种设计模式如何选择

1、单例对象占用资源少,不需要延时加载:

枚举式 好于 饿汉式

2、单例对象占用资源大,需要延时加载:

静态内部类式 好于 懒汉式

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年02月19日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单例模式
    • 单例的使用场景
      • 单例模式优点
        • 单例的实现方式
          • 饿汉式
          • 懒汉式
          • 双重检测锁式
          • 静态内部类式
          • 枚举单例
      • 单例模式漏洞
      • 常见的五种单例模式在多线程环境下的效率测试
      • 总结
        • 1、单例对象占用资源少,不需要延时加载:
          • 2、单例对象占用资源大,需要延时加载:
          相关产品与服务
          数据库
          云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档