如何保证单例模式在多线程中的线程安全性

2024-06-23 12:32

本文主要是介绍如何保证单例模式在多线程中的线程安全性,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

                                                                           如何保证单例模式在多线程中的线程安全性

        对大数据、分布式、高并发等知识的学习必须要有多线程的基础。这里讨论一下如何在多线程的情况下设计单例模式。在23中设计模式中单例模式是比较常见的,在非多线程的情况下写单例模式,考虑的东西会很少,但是如果将多线程和单例模式结合起来,考虑的事情就变多了,如果使用不当(特别是在生成环境中)就会造成严重的后果。所以如何使单例模式在多线程中是安全的显得尤为重要,下面介绍各个方式的优缺点以及可用性:

       1.立即加载(饿汉模式)

        立即加载模式就是在调用getInstance()方法前,实例就被创建了,例:

public class MyObject {
 // 立即加载方式  ==饿汉模式
private static MyObject myObject=new MyObject();
private MyObject(){
}
public static MyObject getInstance(){
return myObject;
}
}

-------------------------------------------------------------------

public class MyThread extends Thread{
public void run(){
System.out.println(MyObject.getInstance().hashCode());
}
}

------------------------------------------------------------------

public class Run {
   public static void main(String[] args) {
 MyThread t1=new MyThread();
 MyThread t2=new MyThread();
 MyThread t3=new MyThread();
 t1.start();
 t2.start();
 t3.start();
}
}

     控制台打印:

714682869
714682869
714682869

    控制台打印出3个相同的hashCode,说明只有一个对象,这就是立即加载的单例模式。但是这种模式有一个缺点,就是不能有其他的实例变量,因为getInstance()方法没有同步,所以可能出现非线程安全问题。

   2.延迟加载(懒汉模式)

    延迟加载就是在getInstance()方法中创建实例,例:

     public class MyObject {
private static MyObject myObject;
private MyObject(){
     }
 public static MyObject getInstance(){
// 延迟加载
 if(myObject!=null){  
}else{
myObject=new MyObject();
 }
return myObject;
     }
}

-------------------------------------------------------------------

public class MyThread extends Thread{
public void run(){
System.out.println(MyObject.getInstance().hashCode());
}
}

-------------------------------------------------------------------

public class Run {
public static void main(String[] args) {
MyThread t1=new MyThread();
t1.start();
}
}

     控制台打印:

1701381926

    控制台打印出一个实例。缺点:在多线程的环境中,就会出现取多个实例的情况,与单例模式的初衷相背离。所以在多线程的环境中,此实例代码是错误的。

    3.延迟加载中使用synchronized修饰方法

    public class MyObject {
private static MyObject myObject;
private MyObject(){
}
synchronized public static MyObject getInstance(){
try {
if(myObject!=null){
}else{
Thread.sleep(3000);
myObject=new MyObject();
}
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
return myObject;
}
}

 -------------------------------------------------------------------

public class MyThread extends Thread{
public void run(){
System.out.println(MyObject.getInstance().hashCode());
}
}      

-------------------------------------------------------------------

public class Run {
public static void main(String[] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
t1.start();
t2.start();
t3.start();
}
}

     控制台打印:

 1069480624
1069480624
1069480624

     虽然得到了相同的实例,但是我们知道synchronized是同步的,一个线程必须等待另一个线程释放锁之后才能执行,影响了效率。

      4.延迟加载中使用同步代码块,对类加锁

        public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
try {
synchronized(MyObject.class){
if(myObject!=null){
}else{
Thread.sleep(3000);
myObject=new MyObject();
}
}
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
return myObject;
}
}

 -------------------------------------------------------------------

public class MyThread extends Thread {
public void run(){
System.out.println(MyObject.getInstance().hashCode());
}
}

 -------------------------------------------------------------------

public class Run {
public static void main(String[] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
t1.start();
t2.start();
t3.start();
}
}

       控制台打印:

1743911840
1743911840
1743911840

       此代码虽然是正确的,但getInstance()方法里的代码都是同步的了,其实也和第三种方式一样会降低效率

       5.使用DCL双检查锁机制

        DCL双检查锁机制即使用volatile关键字(使变量在多个线程中可见)修改对象和synchronized代码块

       public class MyObject {
    private volatile static MyObject myObject;
    private MyObject(){
    }
    public static MyObject getInstance(){
    try {
if(myObject!=null){
}else{
Thread.sleep(3000);
synchronized(MyObject.class){
if(myObject==null){
myObject=new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
// TODO: handle exception
}
    return myObject;
    }
}

 -------------------------------------------------------------------

public class MyThread extends Thread {
public void run(){
System.out.println(MyObject.getInstance().hashCode());
}
}    

 -------------------------------------------------------------------

public class Run {
public static void main(String[] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
t1.start();
t2.start();
t3.start();
}
}

     控制台打印:

798941612
798941612
798941612

      使用DCL双检查锁机制,成功解决了延迟加载模式中遇到的多线程问题,实现了线程安全。其实大多数多线程结合单例模式情况下使用DCL是一种好的解决方案。

       6.使用静态内置类实现单例模式

       public class MyObject {
// 内部类方式
private static class MyObjectHandler{
private static MyObject myObject=new MyObject();
}
private MyObject(){

}
public static MyObject getInstance(){
return MyObjectHandler.myObject;
}
}

-------------------------------------------------------------------

public class MyThread extends Thread {
public void run(){
System.out.println(MyObject.getInstance().hashCode());
}
}

 -------------------------------------------------------------------

public class Run {
public static void main(String[] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
t1.start();
t2.start();
t3.start();

}
}

    控制台打印:

1743911840
1743911840
1743911840

         使用静态内置类可以解决多线程中单例模式的非线程安全的问题,实现线程安全,但是如果对象是序列化的就无法达到效果了。

       7.序列化与反序列化的单例模式

 需要readResolve方法

        public class MyObject implements Serializable{
private static final long serialVersionUID=888L;
// 内部类
private static class MyObjectHandler{
private static final MyObject myObject=new MyObject();
}
private MyObject(){

}
public static MyObject getInstance(){
return MyObjectHandler.myObject;
}
 protected Object readResolve() throws ObjectStreamException {
 System.out.println("调用了readResolve方法");
return MyObjectHandler.myObject;
 }
}

-------------------------------------------------------------------

public class SaveAndRead {
public static void main(String[] args) {
try {
MyObject myObject=MyObject.getInstance();
FileOutputStream fosRef=new FileOutputStream(new File("myObjectFile.txt"));
ObjectOutputStream oosRef=new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
// TODO: handle exception
} catch(IOException e){
e.printStackTrace();
}
try {
FileInputStream fisRef=new FileInputStream(new File("myObjectFile.txt"));
ObjectInputStream iosRef=new ObjectInputStream(fisRef);
MyObject myObject=(MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
// TODO: handle exception
} catch(IOException e){
e.printStackTrace();
} catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}   

      控制台打印:

 1988716027
调用了readResolve方法
1988716027

      调用了readResolve方法后就是单例了,如果我们注释掉readResolve方法,

      控制台打印:

977199748
536468534

       8.使用static代码块实现单例模式

        public class MyObject {
private static MyObject instance=null;
private MyObject(){

}
static {
instance=new MyObject();
}
public static MyObject getInstance(){
return instance;
}
}

-------------------------------------------------------------------

public class MyThread extends Thread{

public void run(){
for (int i = 0; i <5; i++) {
System.out.println(MyObject.getInstance().hashCode());
}
}
}

-------------------------------------------------------------------

public class Run {
public static void main(String[] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
t1.start();
t2.start();
t3.start();

}
}

   控制台打印:

798941612
798941612
798941612

    由此可见,使用static代码块也可以实现单例模式,因为静态代码块在使用类的时候已经执行了。

这篇关于如何保证单例模式在多线程中的线程安全性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

浅析如何保证MySQL与Redis数据一致性

《浅析如何保证MySQL与Redis数据一致性》在互联网应用中,MySQL作为持久化存储引擎,Redis作为高性能缓存层,两者的组合能有效提升系统性能,下面我们来看看如何保证两者的数据一致性吧... 目录一、数据不一致性的根源1.1 典型不一致场景1.2 关键矛盾点二、一致性保障策略2.1 基础策略:更新数

Redis Cluster模式配置

《RedisCluster模式配置》:本文主要介绍RedisCluster模式配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录分片 一、分片的本质与核心价值二、分片实现方案对比 ‌三、分片算法详解1. ‌范围分片(顺序分片)‌2. ‌哈希分片3. ‌虚

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2

SpringBoot3中使用虚拟线程的完整步骤

《SpringBoot3中使用虚拟线程的完整步骤》在SpringBoot3中使用Java21+的虚拟线程(VirtualThreads)可以显著提升I/O密集型应用的并发能力,这篇文章为大家介绍了详细... 目录1. 环境准备2. 配置虚拟线程方式一:全局启用虚拟线程(Tomcat/Jetty)方式二:异步

Java中JSON格式反序列化为Map且保证存取顺序一致的问题

《Java中JSON格式反序列化为Map且保证存取顺序一致的问题》:本文主要介绍Java中JSON格式反序列化为Map且保证存取顺序一致的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录背景问题解决方法总结背景做项目涉及两个微服务之间传数据时,需要提供方将Map类型的数据序列化为co