美文网首页
Java 单例模式

Java 单例模式

作者: Jk_zhuang | 来源:发表于2019-04-18 09:04 被阅读0次

        说到单例模式大家应该都不陌生,就是程序在运行过程中,一个类只允许有一个实例存在于内存当中。例如线程池管理类、缓存管理类、某个模块内存管理类等。这些类之所以只允许一个实例,除了内存消耗方面的考虑外,还有程序正确性的考量。
        例如,假设图片内存管理类有两个实例A和B,那在实例A和B上分别有两个内存缓存,假设把一张图片存放在A,去B拿的时候就拿到空了,显然不合理。
        单例模式要求一个类永远只能返回同一个实例,实现的步骤有两个:
        1.将类的构造方法的修饰符改为private,这样外部将无法实例化这个类;
        2.该类对外提供一个获取实例的方法,内部有一个指向本类的对象引用,当引用为空时,实例化这个类并返回,否则直接返回引用。
    下面给出一些单例模式的实现:

    1.饿汉模式:在类加载的时候直接实例化【可使用】
    public class Singleton {
    
        private static volatile Singleton sInstance = new Singleton();
    
        private Singleton() {
        }
    
        public static Singleton instance() {
            return sInstance;
        }
    }
    

    优点:实现简单,在类被加载时就实例化,避免了线程同步问题;
    缺点:类加载时就实例化,没有懒加载,后面可能用不到这个实例,造成了内存资源的浪费。

    2.懒汉模式(同步方法)【不推荐】
    public class Singleton {
    
        private static Singleton sInstance;
    
        private Singleton() {}
    
        public static synchronized Singleton instance() {
            if (sInstance == null) {
                sInstance = new Singleton();
            }
            return sInstance;
        }
    }
    

    优点:懒加载,线程同步;
    缺点:尽管JDK7对synchronized做了优化(偏向锁、轻量级锁、自旋锁、锁去除),但是即使对象已经实例化了,每次也都要进行同步操作,易造成堵塞,效率低。

    3.懒汉模式(双重检查)【不推荐】
    public class Singleton {
    
        private static volatile Singleton sInstance;
        private static Object mObject = new Object();
    
        private Singleton() {
        }
    
        public static Singleton instance() {
            if (sInstance == null) {
                synchronized (mObject) {
                    if (sInstance == null) {
                        sInstance = new Singleton();
                    }
                }
            }
            return sInstance;
        }
    }
    

    优点:两次check null,中间加必要的同步,实现了线程安全,创建了对象。接下来当第一步判断不为空的时候,就直接返回了,效率也比较高。
    缺点:注意到sInstance前面使用volatile关键字修饰了吗?
    这里说下题外话,JVM在new一个对象的时候,有如下3个步骤:

    • a. 给Singleton的实例分配内存
    • b. 调用构造函数,初始化成员属性字段
    • c. 将sInstance指向这块内存
      首先这个过程不是原子操作,其次JVM允许指令重排,当执行的顺序是a - b - c的时候是没问题,但是当执行的顺序是a - c - b时,线程A执行到c,让出了CPU执行时间片,此时线程B判断sInstance是不为空,直接返回引用,但是此时对象还没创建,用的时候岂不是很尴尬?NPE很有可能即将随之而来。这就是DCL失效问题,这种问题难以跟踪、复现,出现的概率小。
      volatile能在sInstance插入内存屏障,防止指令重排,使每次读取都得去主内存读取,因此能防止这样的问题。
      但指令重排什么的是JVM为程序运行做的优化之一,这样子做不太好,并且每次都得去主内存读取,或多或少也影响到性能。这种优化在《Java 高并发程序设计》等书籍上被称为“丑陋的优化”,因此不推荐使用这种。
    4. 静态内部类【推荐】
    public class Singleton {
    
        private Singleton() {
        }
    
        public static Singleton instance() {
            return SHolder.sInstance;
        }
    
        private static class SHolder {
            private static final Singleton sInstance = new Singleton();
        }
    }
    

    这个看着跟饿汉模式有几分相似?是懒汉加载吗?
    类的静态属性只会在第一次加载类的时候初始化,因此sInstance只会在第一次调用SHolder.sInstance,即外部调用instance()时,才会初始化。
    优点:巧妙利用类的初始化时机,避免了线程同步问题(类在初始化时,其它线程无法进入),同时实现了懒加载;

    5. 枚举【推荐】
    public enum Singleton {
        INSTANCE;
        public void whateverMethod() {
        }
    }
    

    借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。

    相关文章

      网友评论

          本文标题:Java 单例模式

          本文链接:https://www.haomeiwen.com/subject/xghawqtx.html