秦怀杂货店

General Store

  • 首页
  • 文章归档
  • 标签
  • 分类
  • 关于页面

  • 搜索
随便聊聊 数据结构 小游戏 数据库 Docker Springboot 系统设计 雪花算法 分布式 海量ip 最长回文子串 算法 面试题 线程池 多线程 线程 java学习 布隆过滤器 github 架构设计 docsify Git JVM LeetCode 杂货思考 设计模式 Lambda native isAssignableFrom 反射 剑指Offer mybatis SPI JDBC 编程工具 Java基础 集合

设计模式【1.3】-- 为什么饿汉式单例是线程安全的?

发表于 2020-12-26 | 分类于 设计模式 | 0 | 阅读次数 454

我们都知道,饿汉式单例是线程安全的,也就是不会初始化的时候创建出两个对象来,但是为什么呢?

首先定义一个饿汉式单例如下:

 public class Singleton {
    // 私有化构造方法,以防止外界使用该构造方法创建新的实例
    private Singleton(){
    }
    // 默认是public,访问可以直接通过Singleton.instance来访问
    static Singleton instance = new Singleton();
}

之所以是线程安全的,是因为JVM在类加载的过程,保证了不会初始化多个static对象。类的生命周期主要是:

加载-->验证-->准备-->解析-->初始化-->使用-->卸载

上面的代码,实际上类成员变量instance是在初始化阶段的时候完成初始化,所有的类变量以及static静态代码块,都是在一个叫clinit()的方法里面完成初始化。这一点,使用jclasslib可以看出来:

clinit()方法是由虚拟机收集的,包含了static变量的赋值操作以及static代码块,所以我们代码中的static Singleton instance = new Singleton();就是在其中。虚拟机本身会保证clinit()代码在多线程并发的时候,只会有一个线程可以访问到,其他的线程都需要等待,并且等到执行的线程结束后才可以接着执行,但是它们不会再进入clinit()方法,所以是线程安全的。我们可以验证一下:

首先改造一下单例:

public class Singleton {
    // 私有化构造方法,以防止外界使用该构造方法创建新的实例
    private Singleton() {
    }

    // 默认是public,访问可以直接通过Singleton.instance来访问
    static Singleton instance = null;

    static {
        System.out.println("初始化static模块---开始");
        instance = new Singleton();
        try {
            System.out.println("初始化中...");
            Thread.sleep(20 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("初始化static模块----结束");
    }
}

测试代码:

import java.io.*;
import java.lang.reflect.InvocationTargetException;

public class SingletonTests {
    public static void main(String[] args) throws Exception, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("线程1开始尝试初始化单例");
                Singleton singleton = Singleton.instance;
                System.out.println("线程1获取到的单例:" + singleton);
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                System.out.println("线程2开始尝试初始化单例");
                Singleton singleton = Singleton.instance;
                System.out.println("线程2获取到的单例:" + singleton);
            }
        });
        thread1.start();
        thread2.start();
    }
}

运行结果,一开始运行的时候,我们可以看到线程1进去了static代码块,它在初始化,线程2则在等待。

image-20201217141915904

待到线程1初始化完成的时候,线程2也不会再进入static代码块,而是和线程1取得同一个对象,由此可见,static代码块实际上就是线程安全的。

image-20201217143603156

  • 本文作者: 秦怀杂货店
  • 本文链接: http://aphysia.cn/archives/designpattern13
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 随便聊聊 # 数据结构 # 小游戏 # 数据库 # Docker # Springboot # 系统设计 # 雪花算法 # 分布式 # 海量ip # 最长回文子串 # 算法 # 面试题 # 线程池 # 多线程 # 线程 # java学习 # 布隆过滤器 # github # 架构设计 # docsify # Git # JVM # LeetCode # 杂货思考 # 设计模式 # Lambda # native # isAssignableFrom # 反射 # 剑指Offer # mybatis # SPI # JDBC # 编程工具 # Java基础 # 集合
剑指Offer【28】-- 数组中次数出现超过一半的数字
设计模式【2】-- 简单工厂模式了解一下?
  • 文章目录
  • 站点概览
秦怀杂货店

秦怀杂货店

纵然缓慢,驰而不息。

145 日志
19 分类
37 标签
Github E-mail
Creative Commons
0%
© 2022 秦怀杂货店
由 Halo 强力驱动
|
主题 - NexT.Pisces v5.1.4