设计模式-单例模式

单例模式是最简单的一种常用设计模式,单例模式需要确保一个类只有一个实例,这个类称为单例类(1、单例类只有一个实例,2、这个类必须自行创建这个实例,3、这个类必须自行向整个系统提供这个实例),像资源管理器(打印机)、数据库连接池等都是单例模式的实际应用。

基本原则

Java实现单例模式一般需要遵循3个原则:

  1. 私有化构造方法,保证外部类无法创建类实例
  2. 私有的静态的类型引用,保证只有一个变量引用
  3. 提供获取实例的方法getInstance()

实现方法

Java中单例模式有以下5种写法:恶汉式、懒汉式、双重校验锁、静态内部类、枚举类型的单例模式,下面分别介绍以下它们的Java实现和优缺点。

恶汉式

在类加载的时候就创建了实例,简单,不存在线程安全的问题,缺点是:在不需要的对象的时候也白白创建了对象,造成资源浪费。

1
2
3
4
5
6
7
8
9
10
class Singleton { //恶汉式
private static final Singleton singleton = new Singleton(); //在类加载的时候就创建了对象
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}

懒汉式

在需要对象的时候才创建对象,缺点:可能造成线程不安全的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton2 { //懒汉式
private static Singleton2 singleton2 = null;
private Singleton2() {
}
public static Singleton2 getInstance() { //getInstance()方法可能出现线程安全问题
if(singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}

双重校验锁

双重校验锁方法是在懒汉式写法上的改进,为了解决懒汉式可能出现的线程安全问题,给getInstance()方法加上同步锁,如下:

1
2
3
4
5
6
7
8
public static Singleton2 getInstance() {
synchronized (Singleton2.class) { // 加锁
if(singleton2 == null) {
singleton2 = new Singleton2();
}
}
return singleton2;
}

但是这种方式效率很低下,会有很多的加锁操作,所以出现兼顾效率和线程安全的写法:双重检查锁,在synchronized之前做一次singleton == null判断可以减少很多加锁操作,极大提升执行效率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Singleton3 { // 双重校验锁
private static volatile Singleton3 singleton = null;
private Singleton3() {
}
public static Singleton3 getInstance() {
if (singleton == null) { // 避免每次都调用加锁
synchronized (Singleton3.class) {
if (singleton == null) {
singleton = new Singleton3();
}
}
}
return singleton;
}
}

PS.双重校验锁在jdk1.5以后使用volatile关键字(保证只有一个实例)才能正常达到单例效果

静态内部类

静态内部类是创建单例模式的一个很好的方法,静态的内部类只会创建一次,所以是线程安全的

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton4 { // 静态的内部类(静态的内部类只会加载一次,所以是线程安全的)
private static class B {
private static Singleton4 singleton2 = new Singleton4();
}
private Singleton4() {
}
public static Singleton4 getInstance() {
return B.singleton2;
}
}

枚举单例模式的写法

编写一个包含单个元素的模具类型,枚举类型中创建的实例是线程安全的,代码极简单,也是现在很推荐的一种单例模式写法

1
2
3
4
5
6
7
8
9
10
// 调用方法Singeton5.INSTANCE.f();
enum Singleton5 {
INSTANCE;
public int i = 0; // 实例变量
public void f() { // 实例方法
System.out.println("枚举单例模式");
}
}

总结

恶汉式线程安全,但是不能延时加载,资源浪费;懒汉式可以延时加载,但是会存在线程安全问题,加上锁之后效率低下;双重校验锁在jdk1.5之后可以达到单例效果;静态内部类延时加载,减小内存开销,无线程安全问题;枚举不仅能够避免多线程问题,还能防止反序列化重新创建新的对象,写法简单,很好~

Fork me on GitHub