单例模式

全局只存在唯一一个对象,该类负责创建自己的对象,同时确保只有一个对象被创建,分别为懒汉式、饿汉式、双重检查锁模式。

  1. 单例类只能有一个实例(构造方法私有)
  2. 单例类必须自己创建自己的唯一实例(类中有方法来创建对象)
  3. 单例类必须给所有其他对象提供这一实例(必须提供访问单例对象的方法)

饿汉式(线程安全)
这种方式在类加载时就完成了初始化,导致类加载的速度变慢,但是这种方式天生就是线程安全的。

public class Demo1 {

    private static Demo1 demo1 = new Demo1();

    private Demo1() {

    }

    public Demo1 getInstance() {
        return demo1;
    }
}

懒汉式(线程不安全)
这种方式虽然延迟了初始化的过程,但在多线程的情景下是不安全的。

public class Demo1 {

    private static Demo1 demo1;

    private Demo1() {

    }

    public static Demo1 getInstance() {
        if (demo1 == null) {
            demo1 = new Demo1();
        }
        return demo1;
    }
}

DCL双重检查锁模式(线程安全)
线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
1、为什么要进行第一次判空?
在多线程并发的时候,如果不判断空,每个线程都要抢占synchronized锁,而且每次都只有一个线程才能抢占到锁,其他线程要等待它释放锁之后才可以执行。所以第一次判空是为了提高程序的效率。

2、为什么要进行第二次判断空?
如果没有第二次判空,可能线程A和线程B都进到了第一个if块中,此时线程A抢到了锁先执行,线程B等待A执行完成释放锁之后,又可以拿到锁继续创建一个新的对象,这就违反了我们的原则。所以要有第二次判空。

3、为什么变量要加volatile关键字?
为了避免指令重排序,demo1 = new Demo1在我们看来就是一句话而以,可是在虚拟机中,它分为3个指令动作:

  1. 为对象分配空间
  2. 初始化对象
  3. 将引用指向对象的内存空间地址

如果在线程A中执行demo1 = new Demo1是按照132顺序执行,执行到3的时候,demo1已经不为空了。如果此时线程B到达第一个if时,此时demo1 != null,则会直接返回一个半初始化的demo1对象,这时就会产生bug。

public class Demo1 {

    private static Demo1 demo1;

    private Demo1() {

    }

    public static Demo1 getInstance() {
        if (demo1 == null) {
            synchronized (Demo1.class) {
                if (demo1 == null) {
                    demo1 = new Demo1();
                }
            }

        }
        return demo1;
    }
}

上一篇:2021年了,为什么还要学习Java?


下一篇:锁——synchronized