单线程下标准的懒汉式单例类

/*
 * 懒汉式单例类 单线程
 * */
class Singleton {
    //    第一步构造方法私有化
    private Singleton() {
        System.out.println("单例类的构造方法只执行一次!!!");
    }

    //    第二步定义静态成员变量
    private static Singleton instance;

    //    第三步提供静态方法 供外界获取单例
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    }


}


public class Main {

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            System.out.println(Singleton.getInstance().hashCode());
        }


    }

}

程序执行结果:

单例类的构造方法只执行一次!!!
621009875
621009875
621009875
621009875
621009875
621009875
621009875
621009875
621009875
621009875

可以看到这里获取的十个Singleton实例的hashCode均相同 并且Singleton的私有构造方法也只执行了一次
说明这个懒汉式的单例设计模式在单线程的情况下是没有问题的

多线程的懒汉式单例类的问题

现在假设有100个线程去访问 Singleton.getInstance(); 来获取该类的实例化对象

/*
 * 懒汉式单例类  多线程
 * */
class Singleton {
    //    第一步构造方法私有化
    private Singleton() {
        System.out.println("单例类的构造方法只执行一次!!!");
    }

    //    第二步定义静态成员变量
    private static Singleton instance;

    //    第三步提供静态方法 用户获取单例
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    }


}


public class Main {

    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
//            Lambda表达式实现多线程
            new Thread( () -> System.out.println(Singleton.getInstance().hashCode()) ).start();
        }


    }

}

程序执行结果:

单例类的构造方法只执行一次!!!
单例类的构造方法只执行一次!!!
671706977
单例类的构造方法只执行一次!!!
900781510
单例类的构造方法只执行一次!!!
1987199268
单例类的构造方法只执行一次!!!
1034212399
单例类的构造方法只执行一次!!!
877465552
单例类的构造方法只执行一次!!!
1498274127
单例类的构造方法只执行一次!!!
1544435981
1544435981
单例类的构造方法只执行一次!!!
1220988310
1544435981
1220988310
877465552
1987199268
1987199268
1987199268
1103334095
1103334095
1220988310
1103334095
1103334095
1220988310
1220988310
1220988310
1220988310
1220988310
1220988310
1220988310
1220988310
1220988310
.
.
.
.

可以看到Singleton 这个类的实例化方法被执行了很多次 并且得到的实例化对象的hashCode值也不尽相同

分析多线程情况下懒汉式单例设计问题出在哪?

单例设计模式(懒汉式)多线程并发访问的同步问题_多线程
那么这种情况该如何解决呢?
你肯定想到了同步 关键字synchronized

下面就来尝试一下 看看能不能解决该问题。
在方法体上加synchronized

/*
 * 懒汉式单例类 多线程同步问题解决  使用synchronized同步方法
 * */
class Singleton {
    //    第一步构造方法私有化
    private Singleton() {
        System.out.println("单例类的构造方法只执行一次!!!");
    }
    //    第二步定义静态成员变量
    private static Singleton instance;

    //    第三步提供静态方法 用户获取单例
    public  static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
//            Lambda表达式实现多线程
            new Thread( () -> System.out.println(Singleton.getInstance().hashCode()) ).start();
        }
    }

}

程序执行结果:

单例类的构造方法只执行一次!!!
1053857889
1053857889
1053857889
1053857889
1053857889
1053857889
1053857889
1053857889
1053857889
.
.
.

可以看到 问题的确已经解决了
但是 仔细想想 synchronized 关键字的意义 我们把synchronized 用在了方法上相当于把整个方法上了一把锁
每一个线程都要排队等待上一个线程把锁释放之后才能执行该方法

那么这样的执行效率和单线程还有区别吗? 使用多线程的目的在于提高程序的执行效率 那么上面这种方法虽然解决了多线程的同步问题,但是代码的执行效率却是非常的低

改造public static synchronized Singleton getInstance()方法

将同步方法 改成同步代码块

/*
 * 懒汉式单例类 同步代码块解决多线程同步问题
 * */
class Singleton {
    //    第一步构造方法私有化
    private Singleton() {
        System.out.println("单例类的构造方法只执行一次!!!");
    }

    //    第二步定义静态成员变量
    private static Singleton instance;

    //    第三步提供静态方法 用户获取单例
    public static Singleton getInstance() {
        if (instance == null) {

            synchronized (Singleton.class) {
                instance = new Singleton();
            }

        }

        return instance;
    }


}


public class Main {

    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
//            Lambda表达式实现多线程
            new Thread(() -> System.out.println(Singleton.getInstance().hashCode())).start();
        }


    }

}

程序执行结果:

单例类的构造方法只执行一次!!!
单例类的构造方法只执行一次!!!
1034212399
900781510
1034212399
单例类的构造方法只执行一次!!!
1220988310
900781510
900781510
1034212399
1034212399
1034212399
1220988310
1220988310
1220988310
单例类的构造方法只执行一次!!!
1466608663
.
.
.

想必大家已经猜到代码有问题

 if (instance == null) {

            synchronized (Singleton.class) {
                instance = new Singleton();
            }

        }

此时仅仅将 instance = new Singleton();使用同步代码块处理
但是仍然会有多个线程进入if代码块内 只是进入到if代码内部之后再依次排队执行 instance = new Singleton();
那么进入到if代码块内部的线程依然是可以 创建新的实例化对象

说到这相信大家肯定已经知道解决办法了
是的 再在 synchronized 代码块内部再加一个if判空条件即可

最终使用同步代码块加判空操作 解决多线程同步问题

/*
 * 懒汉式单例类
 * */
class Singleton {
    //    第一步构造方法私有化
    private Singleton() {
        System.out.println("单例类的构造方法只执行一次!!!");
    }

    //    第二步定义静态成员变量
    private static Singleton instance;

    //    第三步提供静态方法 用户获取单例
    public static Singleton getInstance() {
        if (instance == null) {

            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }

        }

        return instance;
    }


}


public class Main {

    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
//            Lambda表达式实现多线程
            new Thread(() -> System.out.println(Singleton.getInstance().hashCode())).start();
        }


    }

}

程序执行结果:

单例类的构造方法只执行一次!!!
1868347373
1868347373
1868347373
1868347373
1868347373
1868347373
1868347373
1868347373
1868347373
1868347373
1868347373
1868347373
1868347373
.
.
.

可以看到 程序已经解决了同步问题 并且能够真正的发挥多线程的性能

图解多线程获取单例的执行过程

单例设计模式(懒汉式)多线程并发访问的同步问题_单例类_02