关于java:设计模式13-为什么饿汉式单例是线程安全的

4次阅读

共计 1698 个字符,预计需要花费 5 分钟才能阅读完成。

咱们都晓得,饿汉式单例是线程平安的,也就是不会初始化的时候创立出两个对象来,然而为什么呢?

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

 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 则在期待。

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

正文完
 0