Java设计模式-单例模式

为何要使用单例模式

实际开发中,为了节约系统资源,有时须要确保系统中某个类只有惟一的一个实例,当这个惟一实例建立成功后,就没法再建立一个同类型的其它对象,全部的操做都只能基于这个惟一实例。为了确保实例的惟一性,能够经过单例模式实现。java

最简单的单例类设计

public class Single {

    // 设置instance为静态变量
    private static Single instance = null;

    // 构造方法私有化
    private Single() {}

    // 静态方法-实例化对象
    public static Single getInstance() {
        if (instance == null) {
            // 实例化对象
            instance = new Single();
        }
        return instance;
    }

    public static void main(String[] args) {
        // 经过getInstance()方法获取实例
        Single instance1 = Single.getInstance();
        // 经过getInstance()方法获取实例
        Single instance2 = Single.getInstance();
        // 判断两个对象是否相同,相同则表示获取的是同一个实例,不然不是
        System.out.println(instance1 == instance2);
    }
}

由于构造函数的私有化,因此在类外没法直接建立新的 Single 对象,可是能够经过 Single.getInstance() 方法来获取实例对象。第一次调用 getInstance() 方法时将建立一个惟一实例,再次调用的时候会返回这个实例,从而确保实例对象的惟一性。安全

思考多线程

为何 instance 要设置成静态变量?并发

饿汉式和懒汉式

饿汉式单例类
public class EagerSingle {

    // 定义静态变量instance,在类加载的时候已经将其实例化,只能在类内部调用
    private static EagerSingle instance = new EagerSingle();

    // 构造函数私有化-只能从类内部访问
    private EagerSingle() {}

    // 定义静态方法-获取instance对象,供类外部访问调用
    public static EagerSingle getInstance() {
        return instance;
    }
}

在类加载时,调用 new EagerInstance() 方法将静态变量 instance 初始化,此时类的私有构造方法会被调用,单例类的惟一实例被建立。函数

懒汉式单例类
public class LazySingle {

    // 设置instance为静态变量
    private static LazySingle instance = null;

    // 构造函数私有化
    private LazySingle() {}

    // 静态方法-实例化对象
    public static LazySingle getInstance() {
        if (instance == null) {
            // 实例化instance对象
            instance = new LazySingle();
        }
        return instance;
    }
}

懒汉式单例类在第一次调用 getInstance() 方法时建立惟一实例,与饿汉式单例类不一样,在类加载的时候并不会进行类的实例化操做,这种技术也成为延迟加载技术,即须要的时候才会加载实例。可是为了不在多线程环境下多个线程同时调用 getInstance() 方法形成建立多个实例问题,可使用 sychronized 关键字,以下代码:高并发

public class LazySingle {

    // 设置instance为静态变量
    private static LazySingle instance = null;

    // 构造函数私有化
    private LazySingle() {}

    // 静态方法-实例化对象,使用sychronized关键字,保证同一时刻只可一个线程使用getInstance()方法
    public synchronized static LazySingle getInstance() {
        if (instance == null) {
            // 实例化instance对象
            instance = new LazySingle();
        }
        return instance;
    }
}

该单例类在 getInstance() 方法上使用 sychronized 关键字,保证多线程下同时访问 getInstance() 方法的问题。可是每次调用 getInstance() 方法都须要进行线程锁定判断,高并发下会致使系统性能会大大下降。如何解决呢?由于单例类的目标是保证有且只有一个实例,因此咱们只要对 instance = new LazySingle() 代码加锁便可,无需对整个 getInstance() 方法加锁。所以能够对以上代码进行修改,以下代码:性能

public class LazySingle {

    // 设置instance为静态变量
    private volatile static LazySingle instance = null;

    // 构造函数私有化
    private LazySingle() {}

    // 静态方法-实例化对象,使用sychronized关键字,保证同一时刻只可一个线程使用getInstance()方法
    public static LazySingle getInstance() {
        if (instance == null) {
            synchronized (LazySingle.class) {
                instance = new LazySingle();
            }
        }
        return instance;
    }
}

可是该代码仍是存在缺陷,多线程环境下可能仍是建立了多个对象,缘由以下:线程

当线程 T1 和线程 T2 在同时调用 getInstance() 方法,此时 instance 为 null,因此均可以经过 instance == null 判断,可是由于 instance = new LazySingle() 代码进行了加锁,因此同时只可让一个线程进入,若是线程 T1 拿到了锁,则会建立一个实例。此时线程 T2 处于阻塞状态,等待线程 T1 释放锁,线程 T1 执行完毕后释放锁,线程 T2 拿到锁后一样会执行 instance = new LazySingle() 代码建立一个实例,这样就致使了整个单例类建立了两个实例的问题。因此须要对代码进行改进,能够在 instance = new LazySingle() 代码前再进行一个判断,使用 if (instance == null) 语句,这样当线程 T2 拿到锁后会判断是否建立了实例,若是建立则不会再建立了,代码以下:设计

public class LazySingle {

    // 设置instance为静态变量,使用volatile关键字保证instance共享变量的内存可见性
    private volatile static LazySingle instance = null;

    // 构造函数私有化
    private LazySingle() {}

    // 静态方法-实例化对象,使用sychronized关键字,保证同一时刻只可一个线程使用getInstance()方法
    public static LazySingle getInstance() {
        if (instance == null) {
            synchronized (LazySingle.class) {
                if (instance == null) {
                    instance = new LazySingle();
                }
            }
        }
        return instance;
    }
}

须要注意,使用该方式须要使用 volatile 关键字修改 instance 变量,这样能够保证线程 T1 对 instance 变量的修改对线程 T2 当即可见,不然线程 T2 执行 instance == null 是为 true 的。code

饿汉式与懒汉式比较

饿汉式单例类:在类加载的时候就将本身实例化,它的优势在于无需考虑多线程环境下的访问问题,能够很好的确保实例的惟一性,从调用速度和反应时间上看,饿汉式单例类会优于懒汉式单例类,由于实例一开始就被建立了。可是不管系统在运行的时候是否须要该对象都会在类加载的时候建立,所以从资源利用效率看饿汉式单例类不及懒汉式单例类。并且系统加载的时候就要建立对象,因此加载时间会比懒汉式要长。

懒汉式单例类:在第一次调用时建立实例,无需一直占用系统资源,实现了延迟加载,可是必需要处理多线程的访问问题,即多个线程同时调用建立实例的方法可能致使建立了多个实例的问题。因此须要使用锁机制来控制,这样会影响系统性能。

更好的实现方法

饿汉式单例类会在类加载的时候建立实例,不能实现延迟加载,因此无论用不用单例都会一直占据内存。懒汉式单例类使用锁机制保证线程安全,影响系统性能。那么如何将两者优势结合并克服缺点?使用 Initialization Demand Holder (IoDH) 技术。代码以下:

public class BestSingle {

    // 构造函数私有化
    private BestSingle() {}

    // 定义一个静态内部类,其中建立一个BestSingle实例
    public static class HolderClass {
        private static BestSingle instance = new BestSingle();
    }

    // 静态方法,返回instance对象
    public static BestSingle getInstance() {
        return HolderClass.instance;
    }

    public static void main(String[] args) {
        BestSingle instance1, instance2;
        instance1 = BestSingle.getInstance();
        instance2 = BestSingle.getInstance();
        System.out.println(instance1 == instance2);
    }
}

在单例类中加入一个静态内部类,在内部类中建立单例,再将该单例对象经过 getInstance() 方法返回给外部使用。因为静态单例对象并无做为 BestSingle 的成员变量直接实例化,由于类加载的时候不会建立实例,第一次调用 getInstance() 方法会加载静态内部类 HolderClass,在该类内部定义了一个静态变量 intance,此时会初始化这个变量,因为 getInstance() 方法没有使用任何锁机制,对性能不会形成任何影响。因此经过这种方式便可以实现延迟加载,而且在保证线程安全的基础上不影响系统性能。因此该方法是最好的单例模式实现方法

单例模式适用场景

一、系统只须要一个实例对象,好比系统要求提供一个惟一的序列号生成器或资源管理器,或者考虑资源消耗太大只容许建立一个对象。

二、用户调用单例类的的实例只容许使用一个公共访问点,除了该公共访问点,不能经过其它路径访问该实例。

更多问题

如何对单例模式进行改造,使得系统中某个类的对象能够有有限多个?例如二例、三例等。(改造后的类能够称为多例类)

相关文章
相关标签/搜索