设计模式(二)

  |  

单例设计模式

一个类在程序的运行过程中,只有一个对象。

比如日历类 Calendar.getInstance()

  • 单例特点

    • 不能让外界创建该类的对象:私有构造方法

    • 单例类本身要负责创建该类的唯一对象。

    • 单例类要提供一个静态公开的方法返回该类的唯一对象。

饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton{
//饿汉式 使用final或volatile都有场景
private static final Singleton s=new Singleton();
//私有构造方法 避免直接被new创建 造成不唯一
private Singleton(){

}
//对外提供访问对象的方法
public static Singleton getInstance(){
return s;
}
}

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Singleton {
//懒汉式
//要进行线程安全操作
private static Singleton s = null;

private Singleton() {
}

//同步方法
public synchronized static Singleton getInstance() {
if (s == null) {
//在标准类库中很多地方使用懒加载(lazy-load),改善初始内存开销
s = new Singleton();
}
return s;
}
}

这个实现在单线程环境不存在问题,但是如果处于并发场景,就需要考虑线程安全,最熟悉的就莫过于“双检锁”,其要点在于:

  • 在同步之前进行 null 检查,以尽量避免进入相对昂贵的同步块。
  • 直接在 class 级别进行同步,保证线程安全的类方法调用。

考虑线程问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Singleton {
//懒汉式
//要进行线程安全操作
private static Singleton s = null;
private Singleton() {

}
//同步代码块
public static Singleton getInstance() {
if (s == null) {
synchronized (Singleton.class) {
if (s == null) {
s = new Singleton();
}
}
}
return s;
}
}

使用volatile开明性

这里的 volatile 能够提供可见性,以及保证 getInstance 返回的是初始化完全的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton() {
}

public static Singleton getSingleton() {
if (singleton == null) { // 尽量避免重复进入同步块
synchronized (Singleton.class) { // 同步.class,意味着对同步类方法调用
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

在这段代码中,用的是 volatile 修饰静态变量,当 Singleton 类本身有多个成员变量时,需要保证初始化过程完成后,才能被 get 到。

在现代 Java 中,内存排序模型(JMM)已经非常完善,通过 volatilewrite 或者 read,能保证所谓的 happen-before,也就是避免常被提到的指令重排。换句话说,构造对象的 store 指令能够被保证一定在 volatile read 之前。

静态内部类

1
2
3
4
5
6
7
8
9
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
static class SingletonHolder {
private static final Singleton instance = new Singleton3();
}
}

也有一些人推荐利用内部类持有静态对象的方式实现,其理论依据是对象初始化过程中隐含的初始化锁(有兴趣的话你可以参考jls-12.4.2 中对 LC 的说明),这种和前面的双检锁实现都能保证线程安全,不过语法稍显晦涩,未必有特别的优势。

枚举

1
2
3
public enum Singleton {
instance;
}

注:枚举本质上就是饿汉模式,

Copyright © 2018 - 2020 Kuanger All Rights Reserved.

访客数 : | 访问量 :