关于单例模式的理解与简述

1、什么是单例模式?

单例模式指无论调用实例方法(返回实例的方法)多少次,都保持同一个实例的设计模式。

2、如何实现单例模式?

/**
 * 单例模式类
 */
class Singleton {
    private static Singleton instance;
    private Singleton() {
        
    }

    public static Singleton newInstance() {
        return null;
    }
}

这就是单例模式的实现思路。

2.1 实现思路

类外获取实例的方法不是通过new或者反射,而是通过调用newInstance()。三点说明:

  • 1、instance为静态私有,一是为了静态newInstance()可获取到,二是防止类外直接获取;
  • 2、构造方法设置为私有,即无法主动实例化(new).
  • 3、newInstance()是静态公共方法,static是因为Singleton无法实例化,即无对象可以调用newInstance();public是为了方便类外调用。

3、单例模式有几种形式?

3.1 “饿汉式”:

/**
 * 单例模式类
 */
class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {

    }

    public static Singleton newInstance() {
        return instance;
    }
}

特点:唯一实例在类内直接创建,不存在多实例可能,不会违背“单例”。

3.2 “懒汉式”:

/**
 * 单例模式类
 */
class Singleton {
    private static Singleton instance;
    private Singleton() {

    }

    public static Singleton newInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return new Singleton();
    }
}

特点:当newInstance()被调用时,才创建实例。

3.2.1 线程不安全问题

这个方法表面上看没有问题,可实际上,由于这个方法本身是线程不安全的,当多个线程同时调用时,就可能导致创建多个实例,违背“单例”。举个例子:

    public static Singleton newInstance() {
        if (instance == null) {---------------a
            instance = new Singleton();-------b
        }
        return new Singleton();
    }

有两个线程x、y同时调用newInstance(),x先执行a,判断instance 是否存在,为true,可此时,x的CPU时间片用完,CPU被y抢去。y也执行a,判断instance 是否存在,为true,执行b,创建一个实例。然后,x重新获得时间片,继续执行,由于x已经判断过instance ,为true就执行b,又创建一个实例。这样,就违背了“单例“。
在高并发下,这种情况很容易发生,而且这只是其中一种情况。

4、如何解决“懒汉式”线程安全问题?

4.1 “锁方法”:

    public static synchronized Singleton newInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return new Singleton();
    }
}

将newInstance()用synchronized同步锁锁住,这样一次就只有一个线程能进入newInstance()。

4.2 “锁代码块”:

    public static Singleton newInstance() {
        synchronized (Singleton.class) {
            if (instance == null) {-------------a
                instance = new Singleton();-----b
            }
        }
        return new Singleton();
    }
}

因为可能会出现线程安全问题的代码就是a,故用synchronized 将其锁住,原理与“锁方法”相同。

4.3 “双重检测机制”:

    public static Singleton newInstance() {
        if (instance == null) {--------------------a
            synchronized (Singleton.class) {-------b
                if (instance == null) {------------c
                    instance = new Singleton();----d
                }
            }
        }
        return new Singleton();---------------------e
    }

在上述举例的会导致线程安全的例子中,根本原因就是未对实例存在做双重检测

4.3.1 代码读解:

以上述举例的会导致线程安全的例子为背景。
当x的时间片用完,CPU被y抢去时,y一路畅通,执行d创建实例。然后,x执行c重新判断instance是否存在,由于y已经创建实例,故instance存在,因此,x执行e返回instance,实例唯一。
必须用synchronized将c锁住,不然与“懒汉式”别无二致,无意义。
大家可以模拟各种并发时线程行为,自行推演一下。

文章完结。