Java设计模式--单例模式

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

Singleton Pattern

Ensure a class only has one instance, and provide a global point of access to it.

类图

模式的结构与使用

单例方法模式的结构中包括一种角色。

  • 单件类(Singleton):单件类只可以创建出一个实例。

java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:

  • 懒汉式单例
  • 饿汉式单例
  • 登记式单例

简单的例子

懒汉式单例.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Singleton;
/**
* @author yxx
* 懒汉式单例类,在第一次调用的时候实例化自己
*/
public class Singleton {
private Singleton(){}
private static Singleton single = null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题。

在getInstance方法上加同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Singleton;
/**
* @author yxx
* 懒汉式单例类,在第一次调用的时候实例化自己
* 在getInstance方法上加同步
*/
public class SingletonSynchronized {
private SingletonSynchronized(){}
private static SingletonSynchronized single = null;
//静态工厂方法
public static synchronized SingletonSynchronized getInstance() {
if (single == null) {
single = new SingletonSynchronized();
}
return single;
}
}

双重检查锁定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Singleton;
/**
* @author yxx
* 懒汉式单例类,在第一次调用的时候实例化自己
* 双重检查锁定
*/
public class SingletonDoubleChecked {
private SingletonDoubleChecked(){}
private static SingletonDoubleChecked single = null;
//静态工厂方法
public static SingletonDoubleChecked getInstance() {
if (single == null) {
synchronized (SingletonDoubleChecked.class) {
if (single == null) {
single = new SingletonDoubleChecked();
}
}
}
return single;
}
}

静态内部类,既实现了线程安全,又避免了同步带来的性能影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Singleton;
/**
* @author yxx
* 懒汉式单例类,在第一次调用的时候实例化自己
* 既实现了线程安全,又避免了同步带来的性能影响
*/
public class SingletonStatic {
private static class LazyHolder {
private static final SingletonStatic INSTANCE = new SingletonStatic();
}
private SingletonStatic (){}
public static final SingletonStatic getInstance() {
return LazyHolder.INSTANCE;
}
}

饿汉式单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Singleton;
/**
* @author yxx
* 饿汉式单例类,在类初始化时,已经自行实例化
*/
public class Singleton2 {
private Singleton2() {}
private static final Singleton2 single = new Singleton2();
// 静态工厂方法
public static Singleton2 getInstance() {
return single;
}
}

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。

这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。

饿汉式和懒汉式区别

从名字上来说,饿汉和懒汉,饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

什么是线程安全?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。

单例模式的优点

  • 单例类的唯一实例由单例类本身控制,所以可以很好地控制用户何时访问它。

适用单例模式的情景

  • 当系统需要某个类只能有一个实例。

下载源码请到

MyGitHub

文章目录
  1. 1. 单例模式
  2. 2. Singleton Pattern
  3. 3. 类图
  4. 4. 模式的结构与使用
  5. 5. java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:
  6. 6. 简单的例子
    1. 6.1. 懒汉式单例.java
    2. 6.2. 在getInstance方法上加同步
    3. 6.3. 双重检查锁定
    4. 6.4. 静态内部类,既实现了线程安全,又避免了同步带来的性能影响
    5. 6.5. 饿汉式单例模式
  7. 7. 饿汉式和懒汉式区别
  8. 8. 什么是线程安全?
  9. 9. 单例模式的优点
  10. 10. 适用单例模式的情景
  11. 11. 下载源码请到