Java学习36-Java 多线程安全:懒汉式和饿汉式

2024-04-01 21:36

本文主要是介绍Java学习36-Java 多线程安全:懒汉式和饿汉式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JAVA种有两种保证线程安全的方式,分别叫懒汉式Lazy Initialization和饿汉式Eager Initialization,以下是他们的区别:

  • 线程安全性:

懒汉式本身是非线程安全的,因为多个线程可能同时检查实例是否为null,并尝试同时创建实例,会导致出现多个实例。为了解决这个问题,需要额外的同步机制,如双重检查锁定(double-checked locking)或静态内部类等方式。
而饿汉式最开始就static和final了天生就是线程安全的。

  • 实例创建时机不同:

懒汉式在类被创建时不立即创建实例,而是在第一次调用 类名.getInstance() 方法时才创建实例,实现了延迟加载(非线程安全)。
饿汉式直接在最开始就static final SingletonEager instance = new SingletonEager()直接创建完毕了(自带安全属性)。

  • 资源加载和性能不同:

懒汉式(慢):延迟了实例的创建,只有在真正需要使用时才会进行初始化,因此可以节省资源。但在第一次调用 getInstance() 方法时,由于需要创建实例,可能会有一定的性能延迟。
饿汉式(快,浪费内存):对象在加载时已经创建,因此无论是否适用单例对象,都会占用一定内存。但是由于对象已经提前初始化,第一次调用getInstance方法速度会更快。

饿汉式(本身就是线程安全的)

饿汉式(Eager Initialization)开始就直接创建,不调用也存在,占内存,调用跑起来快,自带线程安全属性。

饿汉式举例:

public class EagerSingleton {  private static final EagerSingleton INSTANCE = new EagerSingleton();  private EagerSingleton() {  // 私有构造函数  }  public static EagerSingleton getInstance() {  return INSTANCE;  }  
}

使用举例:

package ThreadPool;public class Test {public static void main(String[] args) {EagerSingleton e1 = EagerSingleton.getInstance();EagerSingleton e2 = EagerSingleton.getInstance();System.out.println(e1==e2);}}class EagerSingleton{private EagerSingleton() {}private static final EagerSingleton instance = new EagerSingleton();public static EagerSingleton getInstance(){return instance;}
}

运行结果

true

懒汉式

懒汉式(Lazy Initialization)

下面是一个非线程安全的一般懒汉式示例(不建议使用,除非有额外的同步机制):

public class LazySingleton {  private static LazySingleton instance;  private LazySingleton() {  // 私有构造函数  }  public static LazySingleton getInstance() {  if (instance == null) {  instance = new LazySingleton();  }  return instance;  }  
}

线程安全的懒汉式示例(使用双重检查锁定):

第一次判断if (instance == null) 再进行下面的线程synchronized, 如果实例已经存在,直接都不用管线程synchronized那些程序块,直接return输出了。

public class ThreadSafeLazySingleton {  private static volatile ThreadSafeLazySingleton instance;  private ThreadSafeLazySingleton() {  // 私有构造函数  }  public static ThreadSafeLazySingleton getInstance() {  if (instance == null) { // 第一次检查实例是否存在  synchronized (ThreadSafeLazySingleton.class) {  if (instance == null) { // 第二次检查实例是否存在  instance = new ThreadSafeLazySingleton();  }  }  }  return instance;  }  
}

补充知识点

volatile是一个关键字,用于修饰变量。当一个变量被声明为volatile时,它意味着这个变量在多线程环境下是可见的和有序的。这有助于确保线程安全,但它并不保证复合操作的原子性。例如,自增操作++实际上包括读取、增加和写入三个步骤,如果多个线程同时对一个volatile变量进行自增操作,那么结果可能会不正确。在下面的例子中volatile被用在在Bank instance的定义中。

示例2:构建一个银行单例,使用三个线程分别调用它,保证线程安全条件下(三个线程调用的是同一个银行instance),输出“线程名字+ My Private Bank is building up! ”

package ThreadPool;public class Test3 {public static void main(String[] args) {//创建三个BankThread对象BankThread b1 = new BankThread();BankThread b2 = new BankThread();BankThread b3 = new BankThread();//分别启动这三个线程,因为Bank类是单例的,因此所有线程都将获取到同一个Bank对象实例b1.start();b2.start();b3.start();}}//一个专门构建的可以调用Bank类的Thread类
class BankThread extends Thread{@Overridepublic void run() {Bank bank = Bank.getInstance();bank.PrintBank();}
}//构建Bank类,实现了懒汉单例模式
//两层if(instance == null)和 synchronized (Bank.class)确保线程安全
class Bank{private static volatile Bank instance;private Bank() {}static Bank getInstance(){if(instance == null){synchronized (Bank.class){if(instance == null){instance = new Bank();}}}return instance;}public void PrintBank(){System.out.println(Thread.currentThread().getName()+ " My Private Bank is building up! ");}
}

运行输出:


Thread-0 My Private Bank is building up! 
Thread-1 My Private Bank is building up! 
Thread-2 My Private Bank is building up! Process finished with exit code 0

饿汉式和懒汉式的主要区别在于实例的创建时机和线程安全性。饿汉式在类加载时即创建实例,线程安全且性能较高(首次调用速度快),但可能浪费资源(即使实例从未被使用)。懒汉式则延迟了实例的创建,节省了资源,但需要在多线程环境下采取额外的同步措施来保证线程安全。在实际应用中,应根据具体需求选择适合的实现方式。

这篇关于Java学习36-Java 多线程安全:懒汉式和饿汉式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/868192

相关文章

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例:

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.