《Head First 设计模式》读书笔记(5)——单件模式 Singleton

本文共2376词,阅读需要8分钟,本项目相关代码地址被托管在 gitee

关于饿汉和懒汉,经常听见这两个词,尝试辨别一下:
懒汉:就是类加载时,不急着初始化,而是等着判断好需不需要初始化,在决定。主要源于lazy loading这个词
饿汉:就是类一加载就初始化,但是因为静态成员,所以不存在线程问题。

一般说,习惯还是单例模式,实现它的关键一环就是他没有公开的构造器
目的:用来创建独一无二的,只能有一个实例的对象的入场券。

有一些对象其实我们只需要一个,比方说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表(registry)的对象、日志对象、充当打印机、显卡等设备的驱动程序的对象,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,比如:程序的行为异常、资源使用过量,或者是不一致的结果。

我们现在想要的就是一个类可以被多次使用,但其实是一个实例化对象

public class MyClass {
    private MyClass(){

    }
}

上面的代码是合理的,但是私有构造器的类不能用它来实例化,于是就有人这么做:

class MyClass {
    private MyClass() {
    }

    public static MyClass getInstance() {
        return new MyClass();
    }
}
剖析经典的单件模式实现——懒汉式 线程不安全
class Singleton {
    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

在后面,你会看到这个版本有些问题,剧透:这个是线程不安全的,因为在同一时刻可能两个线程都进入了getInstance中创建新的Singleton,导致唯一性不能保证。

处理多线程的单件模式——懒汉式 线程安全

确保一个类只有一个实例,并提供一个全局访问点,上面这个代码看起来好像并没有什么问题,但是对于这个对象,如果希望它顺序执行例如加水——烧热——等煮熟后才能关闭这三个步骤,单线程没问题,但是多线程的话,对于当前实例的状态是不确定的,比如可能会产生两个对象开始发现都为空,于是在JVM中就新建了两个对象,处理这些事情,代码就会出现一些未知的异常。

其实只需要在新建对象的方法上加上synchronized关键字,保证不会有两个线程同时进入到这个方法中

class Singleton {
    private static Singleton singleton;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

但是随之而来的是新的问题,同步会降低性能,因为要进行判断这个方法的状态,一旦这个变量已经实例化了,每次访问还是同步的话,就是一种累赘。你必须要知道:同步一个方法,会使程序运行效率下降100倍

不要延迟,直接实例化——饿汉式——线程安全
class SingletonFast {
    private static SingletonFast singleton = new SingletonFast();

    private SingletonFast() {
    }

    public static SingletonFast getInstance() {
        return singleton;
    }
}

利用这个做法,我们依赖jvm在加载这个类时马上创建此唯一的单件实例,JVM保证在任何线程访问singleton静态变量之前,一定先创建此实例。

双重校验锁 懒汉——线程安全——高性能——比较复杂
class Singleton {
    //volatile关键词确保,当singleton变量被初始化成Singleton实例时,多个线程正确处理singleton变量 
    private volatile static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        //如果不存在,就进入同步区块 
        if (singleton == null) {     
            //只有第一次才彻底执行这里的代码 
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

如果性能是你关注的重点,那么这个做法可以伴你大大的减少时间耗费。

登记式,使用静态内部类 懒汉——线程安全——性能高——难度简单——最实用
public class SingleCommon {
    private static class SingletonHolder {
        private static final SingleCommon INSTANCE = new SingleCommon();
    }

    public static final SingleCommon getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private SingleCommon() {
        init();
    }

    private void init() {

    }
}

这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟饿汉式不同的是:饿汉式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。

推荐之后阅读:
菜鸟教程——单例模式

发表评论

电子邮件地址不会被公开。