设计模式之单例模式
一、单例模式概念
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
二、单例模式结构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FaI5YgyV-1684932357087)(C:\Users\嘉诚\Pictures\Camera Roll\单例模式.png)]
单列模式结构图中只包含一个单例角色:
Singleton
(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()
工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有
;在单例类内部定义了一个Singleton
类型的静态
对象,作为外部共享的唯一实例。
三、单例模式的几种实现方式
1.饿汉模式
-
是否懒加载:否
-
是否多线程安全:是
优点:没有加锁,执行效率高
缺点:类加载时就初始化,浪费内存。它基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
private Mgr01(){}
public static Mgr01 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Mgr01 instance = Mgr01.getInstance();
System.out.println(instance.hashCode());
}
}
}
2.懒汉模式,线程不安全
-
是否懒加载:是
-
是否多线程安全:否
优点:第一次调用才初始化,避免内存浪费。
缺点:在多线程的情况下,不能保证单例。解决方案,加锁
public class Mgr03 {
private static Mgr03 INSTANCE;
private Mgr03(){}
public static Mgr03 getInstance(){
if (INSTANCE == null){
INSTANCE = new Mgr03();
}
return INSTANCE;
}
}
3.懒汉模式,线程安全
- 是否懒加载:是
- 是否多线程安全:是
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
public class Mgr04 {
private static Mgr04 INSTANCE;
private Mgr04(){}
public static synchronized Mgr04 getInstance(){
if (INSTANCE == null){
INSTANCE = new Mgr04();
}
return INSTANCE;
}
}
4.双重检查
- 是否懒加载:是
- 是否多线程安全:是
这种方式称为双重检查锁(Double-Check Locking),需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。
public class Mgr06 {
private static volatile Mgr06 INSTANCE;//JIT
private Mgr06(){}
public static Mgr06 getInstance(){
if (INSTANCE == null){
//双重检查
synchronized (Mgr06.class){
if (INSTANCE == null){
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
}
5.静态内部类
- 是否懒加载:是
- 是否多线程安全:是
该方法在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用。通过jvm来保证单例。加载外部类时不会加载内部类,在第一次调用getInstance()时将加载内部类Mgr07Holder,这样可以实现懒加载
public class Mgr07 {
private Mgr07(){}
private static class Mgr07Holder{
private final static Mgr07 INSTANCE = new Mgr07();
}
public static Mgr07 getInstance(){
return Mgr07Holder.INSTANCE;
}
}
6.枚举
- 是否懒加载:否
- 是否多线程安全:是
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是Effective Java作者Josh Bloch提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际中,也很少用。
public enum Mgr08 {
INSTANCE;
}