美文网首页Java设计模式
设计模式之单例模式

设计模式之单例模式

作者: 于无声处写写写 | 来源:发表于2018-05-11 10:20 被阅读14次

定义:

单例模式确保一个类只有一个实例,并提供一个全局访问点。

应用

比如在一个记事本的设置中,将这个配置文件设定为一个类的对象,这时候应该保证这个配置文件是在所有的打开中都是唯一的。不管打开这个记事本多少次,不管初始化这个配置文件多少次,都应该保证这个配置文件是唯一的。如果这个配置文件不是唯一的,那么每次打开记事本都要初始化软件配置,事实并不能这个样子。所以这时候要保证配置文件是唯一的,就用到了单例模式。

1526004651(1).jpg

懒汉式 线程不安全

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

懒汉式 线程安全

只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。

但是这样有一个问题,就是当一个线程进入该方法之后,其它线程试图进入该方法都必须等待,因此性能上有一定的损耗。

public static synchronized Singleton getUniqueInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

饿汉式 线程安全

线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。

private static Singleton uniqueInstance = new Singleton();

双重校验锁-线程安全

uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行。也就是说,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。

双重校验锁先判断 uniqueInstance 是否已经被初始化了,如果没有被实例化,那么才对实例化语句进行加锁。

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 uniqueInstance = new Singleton(); 这条语句,只是早晚的问题,也就是说会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 判断。

if (uniqueInstance == null) {
    synchronized (Singleton.class) {
        uniqueInstance = new Singleton();
    }
}

uniqueInstance 采用 volatile 关键字修饰也是很有必要的。

uniqueInstance = new Singleton(); 这段代码其实是分为三步执行。

分配内存空间。
初始化对象。
将 uniqueInstance 指向分配的内存地址。
但是由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1>3>2,这在单线程情况下自然是没有问题。但如果是多线程就有可能 B 线程获得是一个还没有被初始化的对象以致于程序出错。

所以使用 volatile 修饰的目的是禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

相关文章

网友评论

    本文标题:设计模式之单例模式

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