zoukankan      html  css  js  c++  java
  • 单例模式——你真的懂单例模式?

    单例模式

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

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

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

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

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

    以下是单例模式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();
            }
        }
    
    }

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

  • 相关阅读:
    计算机网络——TCP如何做到可靠数据传输
    计算机网络——TCP的流水线传输(超详细)
    计算机网络——TCP的拥塞控制(超详细)
    计算机网络——TCP的三次握手与四次挥手(超详细)
    计算机网络——多路复用与多路分解
    转:资源 | 我爱自然语言处理
    bootsect及setup
    python默认编码设置
    实例分析C程序运行时的内存结构
    matlab常用目录操作
  • 原文地址:https://www.cnblogs.com/zhulei2/p/13100083.html
Copyright © 2011-2022 走看看