五种方式实现 Java 单例模式
前言
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
饿汉单例
是否多线程安全:是
是否懒加载:否
正如名字含义,饿汉需要直接创建实例。
public class EhSingleton {
private static EhSingleton ehSingleton = new EhSingleton();
private EhSingleton() {}
public static EhSingleton getInstance(){
return ehSingleton;
}
}
缺点: 类加载就初始化,浪费内存
优点: 没有加锁,执行效率高。还是线程安全的实例。
懒汉单例
懒汉单例,在类初始化不会创建实例,只有被调用时才会创建实例。
非线程安全的懒汉单例
是否多线程安全:否
是否懒加载: 是
public class LazySingleton {
private static LazySingleton ehSingleton;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (ehSingleton == null) {
ehSingleton = new LazySingleton();
}
return ehSingleton;
}
}
实例在调用 getInstance 才会创建实例,这样的优点是不占内存,在单线程模式下,是安全的。但是多线程模式下,多个线程同时执行 if (ehSingleton == null) 结果都为 true,会创建多个实例,所以上面的懒汉单例是一个线程不安全的实例。
加同步锁的懒汉单例
是否多线程安全:是
是否懒加载: 是
为了解决多个线程同时执行 if (ehSingleton == null) 的问题,getInstance 方法添加同步锁,这样就保证了一个线程进入了 getInstance 方法,别的线程就无法进入该方法,只有执行完毕之后,其他线程才能进入该方法,同一时间只有一个线程才能进入该方法。
public class LazySingletonSync {
private static LazySingletonSync lazySingletonSync;
private LazySingletonSync() {}
public static synchronized LazySingletonSync getInstance() {
if (lazySingletonSync == null) {
lazySingletonSync =new LazySingletonSync();
}
return lazySingletonSync;
}
}
这样配置虽然保证了线程的安全性,但是效率低,只有在第一次调用初始化之后,才需要同步,初始化之后都不需要进行同步。锁的粒度太大,影响了程序的执行效率。
双重检验懒汉单例
是否多线程安全:是
是否懒加载:是
使用 synchronized 声明的方法,在多个线程访问,比如A线程访问时,其他线程必须等待A线程执行完毕之后才能访问,大大的降低的程序的运行效率。这个时候使用 synchronized 代码块优化执行时间,减少锁的粒度。
双重检验首先判断实例是否为空,然后使用 synchronized (LazySingletonDoubleCheck.class) 使用类锁,锁住整个类,执行完代码块的代码之后,新建了实例,其他代码都不走 if (lazySingletonDoubleCheck == null) 里面,只会在最开始的时候效率变慢。而 synchronized 里面还需要判断是因为可能同时有多个线程都执行到 synchronized (LazySingletonDoubleCheck.class) ,如果有一个线程线程新建实例,其他线程就能获取到 lazySingletonDoubleCheck 不为空,就不会再创建实例了。
public class LazySingletonDoubleCheck {
private static LazySingletonDoubleCheck lazySingletonDoubleCheck;
private LazySingletonDoubleCheck() {}
public static LazySingletonDoubleCheck getInstance() {
if (lazySingletonDoubleCheck == null) {
synchronized (LazySingletonDoubleCheck.class) {
if (lazySingletonDoubleCheck == null) {
lazySingletonDoubleCheck = new LazySingletonDoubleCheck();
}
}
}
return lazySingletonDoubleCheck;
}
}
静态内部类
是否多线程安全:是
是否懒加载:是
外部类加载时,并不会加载内部类,也就不会执行 new SingletonHolder(),这属于懒加载。只有第一次调用 getInstance() 方法时才会加载 SingletonHolder 类。而静态内部类是线程安全的。
静态内部类为什么是线程安全
静态内部类利用了类加载机制的初始化阶段 方法,静态内部类的静态变量赋值操作,实际就是一个 方法,当执行 getInstance() 方法时,虚拟机才会加载 SingletonHolder 静态内部类,
然后在加载静态内部类,该内部类有静态变量,JVM会改内部生成方法,然后在初始化执行方法 —— 即执行静态变量的赋值动作。
虚拟机会保证 方法在多线程环境下使用加锁同步,只会执行一次 方法。
这种方式不仅实现延迟加载,也保障线程安全。
public class StaticClass {
private StaticClass() {}
private static class SingletonHolder {
private static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static final SingletonHolder getInstance() {
return SingletonHolder.INSTANCE;
}
}
总结
- 饿汉单例类加载就初始化,在没有加锁的情况下实现了线程安全,执行效率高。但是无论有没有调用实例都会被创建,比较浪费内存。
- 为了解决内存的浪费,使用了懒汉单例,但是懒汉单例在多线程下会引发线程不安全的问题。
- 不安全的懒汉单例,使用
synchronized声明同步方法,获取实例就是安全了。 synchronized声明方法每次线程调用方法,其它线程只能等待,降低了程序的运行效率。- 为了减少锁的粒度,使用
synchronized代码块,因为只有少量的线程获取实例,实例是null,创建实例之后,后续的线程都能获取到线程,也就无需使用锁了。可能多个线程执行到synchronized,所以同步代码块还需要再次判断一次。 - 静态内部类赋值实际是调用 方法,而虚拟机保证 方法使用锁,保证线程安全。
五种方式实现 Java 单例模式的更多相关文章
- 五种方式让你在java中读取properties文件内容不再是难题
一.背景 最近,在项目开发的过程中,遇到需要在properties文件中定义一些自定义的变量,以供java程序动态的读取,修改变量,不再需要修改代码的问题.就借此机会把Spring+SpringMVC ...
- Java中创建(实例化)对象的五种方式
Java中创建(实例化)对象的五种方式1.用new语句创建对象,这是最常见的创建对象的方法. 2.通过工厂方法返回对象,如:String str = String.valueOf(23); 3.运用反 ...
- 【开发笔记】- Java读取properties文件的五种方式
原文地址:https://www.cnblogs.com/hafiz/p/5876243.html 一.背景 最近,在项目开发的过程中,遇到需要在properties文件中定义一些自定义的变量,以供j ...
- Java Map集合 遍历 五种方式(包含 Lambda 表达式遍历)
示例代码如下: package com.miracle.luna.lambda; import java.util.HashMap; import java.util.Iterator; import ...
- 【Java基础】Java创建对象的五种方式
Java中创建(实例化)对象的五种方式 1.用new语句直接创建对象,这是最常见的创建对象的方法. 2.通过工厂方法返回对象,如:String str = String.valueOf(23); 3. ...
- Spring事务配置的五种方式(转载)
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分. DataSo ...
- Spring事务配置的五种方式
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分. DataSo ...
- Spring事务配置的五种方式 -- 越往后需要Spring版本越高
第五种 基本零配置 个人感觉第四种也可以 Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪种配置方式, ...
- Spring事务配置的五种方式(转)
前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识.通过这次的学习发觉Spring的事务配置只要把思路理清,还是比较好掌握的. ...
随机推荐
- c++对c的拓展_指针的引用
套用引用公式:Type & ref =val; 假设:type 类型为int * 由公式得 int * & ref = val; // int * *const ref=&va ...
- Redis 未授权访问漏洞【原理扫描】修复方法
漏洞类型 主机漏洞 漏洞名称/检查项 Redis 配置不当可直接导致服务器被控制[原理扫描] 漏洞名称/检查项 Redis 未授权访问漏洞[原理扫描] 加固建议 防止这个漏洞需要修复以下三处问题 第一 ...
- 数据库基础知识详解四:存储过程、视图、游标、SQL语句优化以及索引
写在文章前:本系列文章用于博主自己归纳复习一些基础知识,同时也分享给可能需要的人,因为水平有限,肯定存在诸多不足以及技术性错误,请大佬们及时指正. 11.存储过程 存储过程是事先经过编译并存储在数 ...
- (转)Angular中的拦截器Interceptor
什么是拦截器? 异步操作 例子 Session 注入(请求拦截器) 时间戳(请求和响应拦截器) 请求恢复 (请求异常拦截) Session 恢复 (响应异常拦截器) 转之:http://my.osch ...
- 今天学弟问我pip如何永久换源?
pip如何永久换源 临时使用 我们在使用Python开发的时候,经常要下载第三方模块,最常用的方式就是直接pip install 模块名,但是默认是使用国外的源,从pypi仓库中查找目标模块,不管是网 ...
- 双列集合Map接口 & Collections工具类
HashMap 常用方法 遍历方式 iterator迭代器 ITIT HashTable 继承字典 Hashtable--Properties 文件读写 总结 Collections工具类
- SDDC的Windows初体验-QT篇
前言 如果熟悉爱智和看过我之前文章的朋友见到这篇文章一定会有很大疑问,SDDC 作为智能设备发现控制协议,怎么会用在 windows 上? 这一切还是源自于我巨大的脑洞,因为这段在搞 Windows ...
- 服务器安全加固 - Linux
一.账号和口令 1.1 禁用或删除无用账号 查看 /etc/passwd 文件查看是否有无用的账号,如果存在则删除,降低安全风险. 操作步骤: 使用命令 userdel <用户名> 删除不 ...
- HamsterBear Linux Low Res ADC按键驱动的适配 + LVGL button移植
HamsterBear lradc按键驱动的适配 平台 - F1C200s Linux版本 - 5.17.2 ADC按键 - 4 KEY tablet 驱动程序位于主线内核: drivers/inpu ...
- vmware ubuntu 看不到网卡或连接不到网络
执行以下命令就可以重新请求 dhcp 服务器,一般就可以联网了, ens33 是网卡名称,根据自己的情况替换 sudo dhclient ens33 右上角网络图标消失 # 先停止服务 sudo se ...