• 单例模式(3)


    简介:

    单例模式是一种简单的设计模式,但是要在程序设计中使用好单例模式,却需要注意几个地方。

    单例模式意味着在整个系统中,单例类只能有一个实例对象,且需要自行完成实例化,并始终对外提供同一个实例对象。

    单例模式实现方式:

    饿汉模式:

    public class Singleton {
        //饿汉模式是最简单的实现方式,在类加载时就创建单例类对象
        private static final Singleton instance = new Singleton();
    
        //私有化构造方法,防止外界创建对象
        private Singleton(){}
    
        public static Singleton newInstance(){
            //返回唯一的实例对象
            return instance;
        }
    }

    懒汉模式(单线程版):

    public class Singleton {
    
        private static Singleton instance = null;
    
        //私有化构造方法,防止外界创建对象
        private Singleton(){}
    
        public static Singleton newInstance(){
            // 在需要的时候才去创建的单例对象,如采羊例对象已经创建,再次调用 ηewinstance()方法时
            // 将不会重新创建新的单例对象,而是直接返回之前创建的单例对象
            if (null == instance){
                instance = new Singleton();
            }
            //返回唯一的实例对象
            return instance;
        }
    }

    上述懒汉模式有延迟加载的意思,但是没有考虑到多线程的情况,在多线程的场景中,出现多个线程同时调用newInstance方法创建单例对象,从而导致系统中出现多个单例类的实例,显然是不符合要求的。

    懒汉模式(多线程加锁版):

    public class Singleton {
    
        private static Singleton instance = null;
    
        //私有化构造方法,防止外界创建对象
        private Singleton(){}
    
        public static synchronized Singleton newInstance(){
            // 在需要的时候才去创建的单例对象,如采羊例对象已经创建,再次调用 ηewinstance()方法时
            // 将不会重新创建新的单例对象,而是直接返回之前创建的单例对象
            if (null == instance){
                instance = new Singleton();
            }
            //返回唯一的实例对象
            return instance;
        }
    }

    上述加锁设计虽然解决了多线程安全的问题,但是单例在系统中只有一个实例,每次取实例都得进行加锁解锁操作,这会成为系统的性能瓶颈。

    懒汉模式(双重检测锁方式):错误实现

    public class Singleton {
    
        private static Singleton instance = null;
    
        //私有化构造方法,防止外界创建对象
        private Singleton() {
        }
    
        public static Singleton newInstance() {
            if (null == instance) {//第一次检测
                synchronized (Singleton.class) {//加锁
                    if (null == instance) {//第二次检测
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    由于指令重排优化,可能会导致初始化单例对象和将该对象地址赋值给 instance 字段的顺 序与上面 Java 代码中书 写 的顺序不同 。 例如,线程 A 在创建单例对象时,在构造方法被调用 之前,就为该对象分配了内存空间并将对象的宇段设置为默认值。此时线程 A 就可以将分配的内 存地址赋值给 instance 宇段了,然而该对象可能还没有初始化。线程 B 来调用 newInstance()方法,得 到的就 是未初始 化 完全 的单例对象,这就 会导致系统出 现异常行为 。

    为了解决该问题,我们可以使用 volatile关键字修饰 instance字段。 volatile关键字的一个语义就是禁止指令的重排序优化,从而保证 instance 字段被初始化时,单例对象己经被完全初始化 。

    懒汉模式(双重检测锁方式 + volatile):

    public class Singleton {
    
        private static volatile Singleton instance = null;
    
        //私有化构造方法,防止外界创建对象
        private Singleton() {
        }
    
        public static Singleton newInstance() {
            if (null == instance) {//第一次检测
                synchronized (Singleton.class) {//加锁
                    if (null == instance) {//第二次检测
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    静态内部类方式(推荐):

    public class Singleton {
    
        private static class SingletonHolder {
            private static final Singleton instance = new Singleton();
        }
    
        //私有化构造方法,防止外界创建对象
        private Singleton() {
        }
    
        public static Singleton newInstance() {
            return SingletonHolder.instance;
        }
    }

    熟悉 Java 类加载机制 的读者知道,当第 一次访 问类中的静态字段时,会触发类加载,并且同一个类只加载一次。静态内部类也是如此,类加载过程由类加载器负责加锁,从而保证线程安全。

  • 相关阅读:
    获取 iPhone 上联系人姓名、电话、邮件的代码
    NSDate常用代码范例
    iphone开发之多线程NSThread和NSInvocationOperation
    iphone 定时提醒
    iphone 程序自动登陆
    搞定大厂算法面试之leetcode精讲11剪枝&回溯
    大厂算法面试之leetcode精讲7.双指针
    大厂算法面试之leetcode精讲8.滑动窗口
    大厂算法面试之leetcode精讲15.链表
    大厂算法面试之leetcode精讲10.递归&分治
  • 原文地址:https://www.cnblogs.com/wly1-6/p/10374496.html
Copyright © 2020-2023  润新知