单例模式——你真的懂单例模式?

单例模式

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:私有化构造方法

以下是单例模式7种设计方式以及特点说明:

1,饿汉模式

package com.zl.Singleton;

//类加载到内存后就实例化一个对象,JVM保证线程安全
//经济实惠,推荐使用
//唯有一点小不足:不管用到与否,类装载时就完成实例化
public class Singleton01 { private static final Singleton01 singleton = new Singleton01(); private Singleton01(){ } public static Singleton01 getInstance(){ return singleton; } }

2,懒汉式

package com.zl.Singleton;

import java.util.concurrent.TimeUnit;

//达到了按需初始化的目的,但是带来了更大的问题:线程不安全
public class Singleton02 {

    private static Singleton02 singleton;

    private Singleton02(){
    }

    public static Singleton02 getInstance(){
        if(singleton == null){

            //模拟业务场景,费点时间
            try {
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            singleton = new Singleton02();
        }
        return singleton;
    }

    //测试多线程下,单例模式不成立(多线程非安全)
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //jdk1.8之后的lambda表达式
            new Thread(()->
                    System.out.println(Singleton02.getInstance().hashCode())
            ).start();
        }
    }

}

3,懒汉式+ synchronized

package com.zl.Singleton;

import java.util.concurrent.TimeUnit;

//达到了想要的目的,但效率底下
public class Singleton03 {

    private static Singleton03 singleton;

    private Singleton03(){
    }

    public static synchronized Singleton03 getInstance(){
        if(singleton == null){

            //模拟业务场景,费点时间
            try {
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            singleton = new Singleton03();
        }
        return singleton;
    }

    //测试多线程下,单例模式成立(多线程安全)
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //jdk1.8之后的lambda表达式
            new Thread(()->
                    System.out.println(Singleton03.getInstance().hashCode())
            ).start();
        }
    }

}

4,懒汉式+ synchronized+妄想通过减少同步代码块提高效率(实际不可行)

package com.zl.Singleton;

import java.util.concurrent.TimeUnit;

//达到了想要的目的,但效率底下
public class Singleton04 {

    private static Singleton04 singleton;

    private Singleton04(){
    }

    public static Singleton04 getInstance(){
        if(singleton == null){
            synchronized(Singleton04.class){
                //模拟业务场景,费点时间
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                singleton = new Singleton04();
            }
        }
        return singleton;
    }

    //测试多线程下,单例模式不成立(多线程不安全)
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //jdk1.8之后的lambda表达式
            new Thread(()->
                    System.out.println(Singleton04.getInstance().hashCode())
            ).start();
        }
    }

}

 不可行分析:当A线程来调用 getInstance() 方法,判断 singleton  不为空,在A线程还没有把 singleton new出来,B线程也来调用 getInstance() 方法,判断 singleton  不为空,又new了一次

5,看似完美的“双重校验锁(double check lock)”

package com.zl.Singleton;

import java.util.concurrent.TimeUnit;

//达到了想要的目的,看似完美,en... 确实也算不错
public class Singleton05 {

    private volatile static Singleton05 singleton;//思考为什么加volatile?

    private Singleton05() {
    }

    public static Singleton05 getInstance() {
        if (singleton == null) {
            synchronized (Singleton05.class) {
                if (singleton == null) {
                    //模拟业务场景,费点时间
                    try {
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    singleton = new Singleton05();
                }
            }
        }
        return singleton;
    }

    //测试多线程下,单例模式成立(多线程安全)
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //jdk1.8之后的lambda表达式
            new Thread(() ->
                    System.out.println(Singleton05.getInstance().hashCode())
            ).start();
        }
    }

}

思考实例变量为什么加volatile?

Java JVM内部有一个优化,会对汇编指令进行重排序,会在对象半初始化状态返回对象,造成数据丢失。

我们new一个对象时,JVM内部不是一步到位的,如 Object object = new Object();

 它先new了一个对象,然后初始化,指向分配内存地址,return

加volatile是为了禁止指令重排序

扩展:一线大厂面试题。

面试官:知道单例模式吗?了解过double check lock的单例模式吗?为什么实例变量要加volatile?

6,完美的静态内部类实现

package com.zl.Singleton;

//完美的方法
//JVM保证单例,类只加载一次
//加载外部类时不会加载内部类
public class Singleton06 {

    private Singleton06() {
    }

    private static class Singleton06Holder{
        private final static Singleton06 singleton = new Singleton06();
    }

    public static Singleton06 getInstance() {
        return Singleton06Holder.singleton;
    }

    //测试多线程下,单例模式成立(多线程安全)
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //jdk1.8之后的lambda表达式
            new Thread(() ->
                    System.out.println(Singleton06.getInstance().hashCode())
            ).start();
        }
    }

}

7,更加完美的方法?有,枚举类(无构造方法)

Java创始人之一Joshua 在Effective Java  一书提到这种方式

package com.zl.Singleton;

//不仅解决线程同步,而且可以防止方序列化
public enum  Singleton07 {

    singleton;

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //jdk1.8之后的lambda表达式
            new Thread(() ->
                    System.out.println(singleton.hashCode())
            ).start();
        }
    }

}

总结:其实第一种方式平时就够我们用了,想要追求完美可以考虑后三种,特别是最后两种,特特别最后一种!

原文地址:https://www.cnblogs.com/zhulei2/p/13100083.html