多线程案例-单例模式

"风不来见我 我自去见风"

前面说到了一些关于多线程的话题,那么下面的这几篇文章围绕着多线程来说一些案例,那么第一篇便是多线程案例之单例模式。

什么是单例模式:

单例模式是一种设计模式,确保在整个应用程序中只有一个类的实例,并且提供一个全局访问点。

单例模式的作用:

  1. 控制实例数目:确保类只有一个实例,防止创建多个对象,节省资源。
  2. 全局访问:提供一个全局访问点,可以通过该点访问唯一的实例。
  3. 延迟初始化:单例对象可以在第一次使用时创建,有助于提高性能。
  4. 节约资源:对于需要频繁访问的对象或服务,使用单例模式可以减少内存消耗。

单例常见用例:

  • 数据库连接池:通常我们只需一个连接池来管理多个数据库连接。
  • 日志管理:只有一个日志管理实例,统一处理日志信息。
  • 配置管理:配置类可以在整个应用中共享,同步配置状态。

饿汉版单例:

java 复制代码
public class ThreadHungerSingle {

    private static ThreadHungerSingle instance = new ThreadHungerSingle();

    public static ThreadHungerSingle getInstance() {
        return instance;
    }

    //单例模式最关键部分
    private ThreadHungerSingle() {};
}

饿汉 的意思就是 "迫切",在类的加载的时候,就会被创建出这个单例的实例。

懒汉版单例:

java 复制代码
public class ThreadLazySingle {

    private static ThreadLazySingle instance = null;

    public static ThreadLazySingle getInstance() {
        if(null == instance) {
            instance = new ThreadLazySingle();
        }
        return instance;
    }

    private ThreadLazySingle() {};
}

懒汉 的特点就是 "懒",在计算机中,"懒"是一个褒义词呢。只有在被用到时,才会真正创建出实例。

那么对于上述的两个版本,如果是在多线程环境下,调用getInstance时,谁会存在线程安全问题呢?

显然地是我们的懒汉版单例在多线程环境下会存在问题,饿汉版只是单纯的"读"操作,而懒汉版会有"写"操作,下面我们一起来看看这个图便知道了:

存在线程安全问题的话,此时我们就需要进行 加锁 操作来解决了,那么代码如下:

java 复制代码
class ThreadLazySingle2 {
    private static ThreadLazySingle2 instance = null;
    private static final Object locker = new Object();
    public static ThreadLazySingle2 getInstance() {
        synchronized (locker) {
            if(null == instance) {
                instance = new ThreadLazySingle2();
            }
        }
        return instance;
    }

    private ThreadLazySingle2() {};
}

加了锁之后确实解决了线程安全问题,但是加锁同样也可能带来阻塞。如果上述代码,已经new完对象了,那么后续的代码都是单纯的"读"操作,此时调用getInstance也是线程安全的,那么就没有必要加锁了。所以我们可以在前面加上同样的判断:

java 复制代码
class ThreadLazySingle2 {

    private static ThreadLazySingle2 instance = null;
    private static final Object locker = new Object();

    public static ThreadLazySingle2 getInstance() {

        if(null == instance) {
            synchronized (locker) {
                if(null == instance) {
                    instance = new ThreadLazySingle2();
                }
            }
        }
        return instance;
    }

    private ThreadLazySingle2() {};
}

上述两个相同的判断条件看起来好像是有问题的,但是它们各有各的用处,第一个条件判断,判断的是是否需要进行加锁,第二个条件判断,判断的是是否需要进行实例对象。对于锁之间的阻塞,我们无法预测它们会阻塞多久,可能会是"沧海桑田"。

指令重排序:

上述代码中,还存在一个问题,可能会因为编译器的优化带来指令重排序的线程安全问题。什么是指令重排序呢:是计算机体系结构中的一种优化技术,主要指处理器或编译器在不改变程序最终结果的前提下,重新排列指令的执行顺序,以提高性能(如减少流水线停顿、提高缓存命中率等)。

instance = new ThreadLazySingle2();这里我分为三步进行举例说明:1.分配地址空间 2.执行构造方法 3.内存空间的地址,赋值给引用变量。我们一起来看一看,在多线程环境下,他会存在什么样的问题呢:

怎样解决指令重排序的问题呢,也是使用我们的关键字 volatile ,加上 volatile 关键字之后,编译器就会发现 instance 是 "易失" 的,便不会进行上述的优化,所以我们的最终版本是这样的:

java 复制代码
class ThreadLazySingle1 {
    private static volatile ThreadLazySingle1 instance = null;
    private static final Object locker = new Object();

    public static ThreadLazySingle1 getInstance() {
        //此处的if判定是否需要加锁
        if(null == instance) {
            //由于只需要第一次创建出来实例就行,后续的代码,都是单纯的 读 操作,此时 getInstance不加锁也是 线程安全的,没有必要加锁
            //当前写法,只要调用getInstance,都会触发加锁操作,此时虽然没有加锁操作了,但是也会因为加锁,产生阻塞,影响到性能
            synchronized (locker) {
                //此处的if判断是否要创建对象
                if(null == instance) {
                    //这个代码可能会因为指令重排序,引起线程安全问题 所以上面应该加上 volatile 关键字
                    instance = new ThreadLazySingle1();
                }
            }
        }
        return instance;
    }

    private ThreadLazySingle1() {};
}

因此 Java 的volatile 有两个功能:1.保证内存可见性 2.禁止指令重排序

好了,我们本期的内容就讲到这里了,我们下期再见!!!

相关推荐
张张张31214 分钟前
4.2学习总结 Java:list系列集合
java·学习
KATA~17 分钟前
解决MyBatis-Plus枚举映射错误:No enum constant问题
java·数据库·mybatis
SuperW29 分钟前
linux课程学习二——缓存
学习
xyliiiiiL33 分钟前
一文总结常见项目排查
java·服务器·数据库
shaoing34 分钟前
MySQL 错误 报错:Table ‘performance_schema.session_variables’ Doesn’t Exist
java·开发语言·数据库
腥臭腐朽的日子熠熠生辉1 小时前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian1 小时前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之1 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码2 小时前
Spring Task 定时任务
java·前端·spring
俏布斯2 小时前
算法日常记录
java·算法·leetcode