Java面试(五)设计模式
文章目录
- 单例模式
- 懒汉式
- 饿汉式
- 双检锁/双重校验锁(DCL,即 double-checked locking)
- 单例模式相关的问题
- 为什么双检索单例模式中的唯一变量要使用volatile关键字来修饰?
- 单例模式中的唯一实例为什么要设置为静态的?
- 待更新
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 构造函数是私有的。
懒汉式
注意:
- 是否 Lazy 初始化:是
- 是否多线程安全:是
- 实现难度:易
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式
- 是否 Lazy 初始化:否
- 是否多线程安全:是
- 实现难度:易
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
双检锁/双重校验锁(DCL,即 double-checked locking)
- 是否 Lazy 初始化:是
- 是否多线程安全:是
- 实现难度:较复杂
- 描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
单例模式相关的问题
为什么双检索单例模式中的唯一变量要使用volatile关键字来修饰?
主要是因为 singleton = new Singleton() 并不是一个原子指令,在多线程的环境下可能报错。创建一个对象实例,可以分为三步:
- 分配对象内存
- 调用构造器方法,执行初始化
- 将对象引用赋值给变量。
虚拟机实际运行时,以上指令可能发生重排序。以上代码 2,3 可能发生重排序,但是并不会重排序 1 的顺序。也就是说 1 这个指令都需要先执行,因为 2,3 指令需要依托 1 指令执行结果。
Java 语言规规定了线程执行程序时需要遵守 intra-thread semantics。intra-thread semantics 保证重排序不会改变单线程内的程序执行结果。这个重排序在没有改变单线程程序的执行结果的前提下,可以提高程序的执行性能。
虽然重排序并不影响单线程内的执行结果,但是在多线程的环境就带来一些问题。
public class Singleton {
private static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
上面错误双重检查锁定的示例代码中,如果线程 1 获取到锁进入创建对象实例,这个时候发生了指令重排序。当线程1 执行到 t3 时刻,线程 2 刚好进入,由于此时对象已经不为 Null,所以线程 2 可以自由访问该对象。然后该对象还未初始化,所以线程 2 访问时将会发生异常。所以需要使用volatile
修饰singleton。
原文链接
单例模式中的唯一实例为什么要设置为静态的?
单例模式是为了让类的使用者只会创建出一个类的实例,要想达到这样的目的:
- 类的构造方法肯定要对其他类隐藏起来
- 自己要创建一个实例供其他类来使用
- 要提供一个方法给其他类来访问这个实例
要想访问一个类中的方法有两个方式
:
- new一个对应类的对象,在通过对象.方法()的方式来调用成员方法
- 通过类名.方法名()这样来调用对应的类方法
由于单例模式中为了避免外界直接使用构造方法创建实例对象,将构造方法隐藏了起来。此时第一种方法显然无法实现,只有通过第二种方法来获取实例,所以单例模式中的getInstance()方法必须被定义为类方法,即必须加上static。而根据Java语法的要求,在static的环境中是不允许访问非static的成员变量的