单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 单例模式,懒汉式,线程不安全
*/
public class Singleton {
private static Singleton uniqueInstance;

private Singleton() {}

public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 单例模式,饿汉式,线程安全
*/
public class Singleton2 {
private static Singleton2 instance = new Singleton2();

private Singleton2() {}

public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}

return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 单例模式,饿汉式,线程安全,多线程环境下效率不高
*/
public class Singleton3 {
private static Singleton3 instance = null;

private Singleton3() {}

public static synchronized Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}

return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 单例模式,饿汉式,由 static 块保证线程安全
*/
public class Singleton4 {
private static Singleton4 instance;

static {
instance = new Singleton4();
}

private Singleton4() {}

public static Singleton4 getInstance() {
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 单例模式,懒汉式,使用静态内部类,线程安全【推荐】
*/
public class Singleton5 {

private class SingletonHolder {
private static final Singleton5 INSTANCE = new Singleton5();
}

private Singleton5() {}

public static Singleton5 getInstance() {
return SingletonHolder.INSTANCE;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 使用枚举方式,线程安全【推荐】
*
* 枚举自己处理序列化
*/
public enum Singleton6 {
//调用 Singleton6.INSTANCE.whateverMethod()
INSTANCE

public void whateverMethod() {
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 使用双重校验锁,线程安全【推荐】
*/
public class Singleton7 {
private volatile static Singleton7 instance = null;

private Singleton7() {}

public static Singleton7 getInstance() {
if (instance == null) {
synchronized (Singleton7.class) {
if (instance == null) {
instance = new Singleton7();
}
}
}

return instance;
}
}

小结

推荐:静态内部类、枚举、双重校验锁

最好的单例模式:枚举

枚举

使用枚举实现单例模式是最好的方法,因为

  1. 写法简单

    1
    2
    3
    4
    5
    6
    7
    public enum Singleton6 {
    //调用 Singleton6.INSTANCE.whateverMethod()
    INSTANCE

    public void whateverMethod() {
    }
    }
  2. 枚举实例创建是线程安全的

    当一个Java类第一次被真正使用到的时候,静态资源被初始化。Java类的加载和初始化过程都是线程安全的。enum类型会被编译器编译成class T extends Enum的类。所以,创建一个enum类型是线程安全的。

  3. 枚举自己处理序列化

    普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。

    关于枚举类的序列化和反序列化:在序列化的时候Java仅仅是将枚举对象的属性输出到结果中,反序列化的时候则是通过java.lang.EnumvalueOf方法来根据名字查找枚举对象。同时,编译器不允许对这种序列化机制进行定制,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

双重校验锁

volatile 关键字的含义:

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,新值对其他线程是立即可见的
  • 禁止进行指令重排序

volatile 关键字在本实现的主要作用:禁止进行指令重排序。因为类初始化分两步:
volatile 的不足:无法保证原子性,所以要结合synchronized实现

volatile 的使用条件:

  • 对变量的写操作不依赖于当前值
  • 该变量没有包含在具有其他变量的不变式中 (a <= b)

静态内部类

优点:延迟加载、线程安全、访问成本低

延迟加载原理:类级内部类只有在第一次被使用的时候才被会装载。
getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,内部类SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次。

线程安全:由虚拟机来保证它的线程安全性。

访问成本低:getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器