java:单例模式的五种实现方式[通俗易懂]

java:单例模式的五种实现方式[通俗易懂]基于java实现到单例模式

大家好,又见面了,我是你们的朋友全栈君。

1. 概述

java单例模式是一种常见的设计模式。

单例模式有以下特点:

  1. 单例类只能有一个实例;
  2. 单例类必须自己创建自己的唯一实例;
  3. 单例类必须给所有其他对象提供这一实例;

2. 优缺点

  • 优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统运行效率。
  • 缺点:因为系统中只有一个实例,导致了单例类的职责过重,违背了“单一职责原则”,同时不利于扩展。

3. 单例模式实现方式

常见的单例模式实现方式有五种:饿汉式懒汉式双重检测锁静态内部类枚举单例

3.1 饿汉式

public class SingletonDemoInHunger { 
   
	
	// 私有实例,类初始化就加载
	private static SingletonDemoInHunger instance = new SingletonDemoInHunger();
	
	// 私有构造方法
	private SingletonDemoInHunger() { 
   }
	
	// 公共的、静态的获取实例方法
	public static SingletonDemoInHunger getInstance() { 
   
		return instance;
	}
}

饿汉式

  • 类加载时就初始化,浪费内存,不能延迟加载
  • 基于 classloader 机制避免了多线程的同步问题,线程安全
  • 没有加锁,调用效率高

3.2 懒汉式

public class SingletonDemoInLazy { 
   
	
	// 私有实例,初始化的时候不加载(延迟加载)
	private static SingletonDemoInLazy instance;
	
	// 私有构造
	private SingletonDemoInLazy() { 
   }
	
	// 公共获取实例方法(线程不安全)
	public static SingletonDemoInLazy getInstance() { 
   
		if(instance == null ) { 
    // 使用的时候加载
			instance = new SingletonDemoInLazy();
		}
		return instance;
	}
}

上面这种写法,是线程不安全的,但是可以做到延迟加载

下面是线程安全的懒汉模式:

public class SingletonDemoInLazy { 
   
	
	// 私有实例,初始化的时候不加载(延迟加载)
	private static SingletonDemoInLazy instance;
	
	// 私有构造
	private SingletonDemoInLazy() { 
   }
	
	// 公共获取实例方法(线程安全,调用效率低)
	public synchronized static SingletonDemoInLazy getInstance() { 
   
		if(instance == null ) { 
   
			instance = new SingletonDemoInLazy();
		}
		return instance;
	}
}

上面代码中,通过关键字synchronized声明公共的获取实例的方法getInstance(),可以确保线程安全,能做到延迟加载,但是效率不高

3.3 double-checked locking(双重检查锁)

public class SingletonDemoInDoubleCheckLock { 
   
	
	// 私有实例,volatile关键字,禁止指令重排。
	private volatile static SingletonDemoInDoubleCheckLock instance;
	
	// 私有构造
	private SingletonDemoInDoubleCheckLock() { 
   }
		
	// 公共获取实例方法(线程安全)
	public static SingletonDemoInDoubleCheckLock getInstance() { 
   
		if(instance == null ) { 
    // 一重检查
			synchronized (SingletonDemoInDoubleCheckLock.class) { 
   
				if(instance == null) { 
    // 二重检查
					instance = new SingletonDemoInDoubleCheckLock();
				}
			}
		}
		return instance;
	}
}

在加锁之前判断是否为空,可以确保 instance 不为空的情况下,不用加锁,可以直接返回。

加锁之后,还需要判断 instance 是否为空,是为了防止在多线程并发的情况下,会实例化多个对象。例如:线程 a 和线程 b 同时调用 getInstance 方法,假如同时判断 instance 都为空,这时会同时进行抢锁。假如线程 a 先抢到锁,开始执行 synchronized 关键字包含的代码,此时线程 b 处于等待状态。线程 a 创建完新实例了,释放锁了,此时线程 b 拿到锁,进入 synchronized 关键字包含的代码,如果没有再判断一次 instance 是否为空,则可能会重复创建实例。

双重检查锁

  • 双重判断,延迟加载;
  • 线程安全;
  • JDK 版本要求1.5起。

3.4 静态内部类

public class SingletonDemoInStaticInnerClass { 
   
	
	// 静态内部类
	private static class InnerClass{ 
   
		// 初始化实例
		private final static SingletonDemoInStaticInnerClass INSTANCE = new SingletonDemoInStaticInnerClass();
	}
	
	// 私有构造
	private SingletonDemoInStaticInnerClass() { 
   }
	
	// 公关获取实例方法(线程安全,延迟加载)
	public static SingletonDemoInStaticInnerClass getInstance() { 
   
		return InnerClass.INSTANCE;
	}
}

静态内部类

  • 利用了classloader机制来保证初始化 instance 时只有一个线程,线程安全
  • 只有通过显式调用 getInstance 方法时,才会显式装载静态内部类,从而实例化instance延迟加载

3.5 枚举

public enum SingletonEnum { 
   
	
	// 枚举元素本身就是单例 
	INSTANCE;
	
	
	// 其他要执行的方法
	public void sayHello() { 
   
		System.out.println("你好");
	}
	......
}

枚举:这是实现单例模式的最佳方法。它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。但是不是延迟加载的。

如何选用哪种方式实现单例模式?
一般情况下,不建议懒汉式,建议使用饿汉式;只有在要明确实现延迟加载效果时,才会使用静态内部类;如果涉及到反序列化创建对象时,可以尝试使用枚举;如果有其他特殊的需求,可以考虑使用双重检查锁。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/156328.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Opengles2.0入门「建议收藏」

    Opengles2.0入门「建议收藏」一二三四Opengles2.0渲染管线简单画图步骤着色器语言简单介绍镜像技术一OpenGLES2.0渲染管线1.基本处理初始化3D空间中物体的顶点坐标,顶点对应的颜色,顶点的纹

    2022年7月1日
    28
  • vuedevtools使用_怎么下载vue_devtools

    vuedevtools使用_怎么下载vue_devtoolsCSDN首页首页博客程序员学院下载论坛问答代码直播电子书最牛小程序:想要的资源都能搜到?会员中心收藏动态消息15创作中心vue调试工具vue-devtools安装及使用(最新)清虚桂意2020-06-2310:27:29606已收藏4分类专栏:vue版权github克隆vue-devtools官方项目地址gitclone-bv5.1.1https://github.com/vuejs/vue-devtools.git1此处安装v5.1

    2022年10月5日
    4
  • 将war文件解压到指定目录

    将war文件解压到指定目录

    2021年9月15日
    143
  • java grouping,介绍 Java 8 groupingBy Collector

    java grouping,介绍 Java 8 groupingBy Collector介绍Java8groupingByCollector本文我们探讨下Java8groupingByCollector,通过不同的示例进行详细讲解。GroupingByCollectorJava8StreamAPI提供了声明方式处理流数据。static工厂方法Collectors.groupingBy()和Collectors.groupingByConcurrent()…

    2022年8月20日
    9
  • dex文件介绍

    dex文件介绍搭建环境Ubuntu18.04.1LTSandroid-ndk-r17-beta2FFmpeg4.0.2编译FFmpeg修改configure配置编译脚本报错调整错误1:错误2:错误3:错误4:错误5:搭建环境Ubuntu18.04.1LTS需要安装yasm:sudoapt-getinstallyasmand…

    2022年6月27日
    33
  • 谈谈你对map的理解_java中map遍历

    谈谈你对map的理解_java中map遍历摘要:  HashMap是Map族中最为常用的一种,也是JavaCollectionFramework的重要成员。本文首先给出了HashMap的实质并概述了其与Map、HashSet的关系,紧接着给出了HashMap在JDK中的定义,并结合源码分析了其四种构造方式。最后,通过对HashMap的数据结构、实现原理、源码实现三个方面的剖析,深入到它底层Hash存储机…

    2025年9月3日
    8

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号