手记

设计模式 - 单例模式

模式定义

单例模式确保一个类只有一个实例,并且对外提供全局的访问方法。

单例模式有三个特点:

  • 一是一个类只有一个实例

  • 二是它必须自行创建这个实例

  • 三是它必须对外提供全局访问方法

模式类图

以下是单例模式的UML类图

代码实现

一般包含三个要素:
1.私有的静态实例对象 private static instance
2.私有的构造函数(保证在该类外部,无法通过new的方式来创建对象实例) private Singleton(){}
3.公有的、静态的、访问该实例对象的方法 public static Singleton getInstance(){}

懒汉式单例模式(线程不安全-延迟加载)

懒汉式就是应用刚启动的时候不创建实例,当外部调用获取该类的实例方法时才创建。是以** 时间换空间 ** 。
实例被延迟加载,这样做的好处是,如果没有用到该类,那么静态变量instance就不会被实例化,从而节省资源。
懒汉式单例模式是线程不安全的,在多线程环境下instance可能会被多次实例化。

/**
 * Singleton 懒汉式单例模式(线程不安全-延迟加载)
 */public class Singleton {    private static Singleton instance;    private Singleton(){}    public static Singleton getInstance() {        if (instance == null) {
            instance = new Singleton();
        }        return instance;
    }
}

懒汉式单例模式(线程安全-延迟加载)

对getInstance()方法加锁之后,同一时间只有一个线程访问该方法,这样就避免了instance被多次实例化。
getInstance()方法由于加了synchronized关键字,当有一个线程获得锁并访问该方法时,其他线程处于阻塞状态,一定程度上对性能有所损耗。

/**
 * Singleton 懒汉式单例模式(线程安全-延迟加载)
 */public class Singleton {    private static Singleton instance;    private Singleton(){}    public static synchronized Singleton getInstance() {        if (instance == null) {
            instance = new Singleton();
        }        return instance;
    }
}

饿汉式单例模式(线程安全)

饿汉式就是应用刚启动的时候,不管外部有没有调用该类的实例方法,该类的实例就已经创建好了。是以 ** 空间换时间 ** 。
饿汉式单例在类初始化时就创建好一个静态的对象供外部使用,所以本身就是线程安全的。

/** 
 * Singleton 饿汉式单例模式(线程安全)
 */public class Singleton {    private static Singleton instance = new Singleton();    private Singleton(){}    public static Singleton getInstance() {        return instance;
    }
}

双重校验锁单例模式(线程安全-延迟加载)

/**
 * Singleton 双重校验锁单例模式(线程安全-延迟加载)
 *
 * @author renguangli 2018/7/23 16:59
 * @since JDK 1.8
 */public class Singleton {    private static volatile Singleton instance;    private Singleton(){}    public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }        return instance;
    }
}

在进入synchronized块之前加入判空逻辑,只有instance在没有被实例化之前才进入同步块,instance实例化之后就不会在进入同步块里了,效率当然也会提高。

是否有必要加volatile关键字?

我们都知道instance = new Singleton()这段代码是分三步运行的
1、分配内存空间
2、实例化对象
3、将instance指向分配的内存地址

由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1>3>2,这在单线程情况下自然是没有问题。但如果是多线程下,有可能获得是一个还没有被初始化的实例,以致于程序运行出错。

使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。所以还是有必要加上volatile关键字的。

静态内部类实现(线程安全-延迟加载)

/**
 * Singleton 静态内部单例模式(线程安全-延迟加载)
 */public class Singleton {    private static class SingletonHolder{        private static Singleton instance = new Singleton();
    }    private Singleton(){}    public static Singleton getInstance() {        return SingletonHolder.instance;
    }
}

由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。

由于在调用 SingletonHolder.instance 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。
所以这种形式,很好的避免了反射入侵。

基于cas实现的单例模式(线程安全-延迟加载)

/**
 * Singleton cas实现的单例模式(线程安全-延迟加载)
 */public class Singleton {    public static AtomicInteger count = new AtomicInteger(0);    private Singleton() {}    public static Singleton getInstance() {        for (;;) {
            Singleton singleton = INSTANCE.get();            if (null != singleton) {                return singleton;
            }

            singleton = new Singleton();            if (INSTANCE.compareAndSet(null, singleton)) {                return singleton;
            }
        }
    }
}

cas实现的单例模式,它可以保证获取到的实例是唯一的,但是不能保证instance被多次实例化.

基于枚举实现的单例模式

/**
 * 枚举实现的单例模式(线程安全)
 */public enum  Singleton {
    INSTANCE;
}

这是 Effective Java 极力推荐的一种,代码为各种实现中最简单的,其实现,完全是基于枚举类的特性,可以说天生受到了 JVM 的支持,而且既不用思考反射,也不用考虑多线程,对于自身的循环引用,本质上也是一个对象。

原文出处:https://www.cnblogs.com/renguangli/p/9371966.html

0人推荐
随时随地看视频
慕课网APP