大家好,又见面了,我是你们的朋友全栈君。
目录
- 1. 概述
- 2. 优缺点
- 3. 单例模式实现方式
-
- 3.1 饿汉式
- 3.2 懒汉式
- 3.3 double-checked locking(双重检查锁)
- 3.4 静态内部类
- 3.5 枚举
1. 概述
java单例模式是一种常见的设计模式。
单例模式有以下特点:
- 单例类只能有一个实例;
- 单例类必须自己创建自己的唯一实例;
- 单例类必须给所有其他对象提供这一实例;
2. 优缺点
- 优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统运行效率。
- 缺点:因为系统中只有一个实例,导致了单例类的职责过重,违背了“单一职责原则”,同时不利于扩展。
3. 单例模式实现方式
常见的单例模式实现方式有五种:饿汉式、懒汉式、双重检测锁、静态内部类和枚举单例。
3.1 饿汉式
代码语言:javascript复制public class SingletonDemoInHunger {
// 私有实例,类初始化就加载
private static SingletonDemoInHunger instance = new SingletonDemoInHunger();
// 私有构造方法
private SingletonDemoInHunger() {
}
// 公共的、静态的获取实例方法
public static SingletonDemoInHunger getInstance() {
return instance;
}
}
饿汉式:
- 类加载时就初始化,浪费内存,不能延迟加载;
- 基于
classloader
机制避免了多线程的同步问题,线程安全; - 没有加锁,调用效率高。
3.2 懒汉式
代码语言:javascript复制public class SingletonDemoInLazy {
// 私有实例,初始化的时候不加载(延迟加载)
private static SingletonDemoInLazy instance;
// 私有构造
private SingletonDemoInLazy() {
}
// 公共获取实例方法(线程不安全)
public static SingletonDemoInLazy getInstance() {
if(instance == null ) {
// 使用的时候加载
instance = new SingletonDemoInLazy();
}
return instance;
}
}
上面这种写法,是线程不安全的,但是可以做到延迟加载。
下面是线程安全的懒汉模式:
代码语言:javascript复制public class SingletonDemoInLazy {
// 私有实例,初始化的时候不加载(延迟加载)
private static SingletonDemoInLazy instance;
// 私有构造
private SingletonDemoInLazy() {
}
// 公共获取实例方法(线程安全,调用效率低)
public synchronized static SingletonDemoInLazy getInstance() {
if(instance == null ) {
instance = new SingletonDemoInLazy();
}
return instance;
}
}
上面代码中,通过关键字synchronized
声明公共的获取实例的方法getInstance()
,可以确保线程安全,能做到延迟加载,但是效率不高。
3.3 double-checked locking(双重检查锁)
代码语言:javascript复制public class SingletonDemoInDoubleCheckLock {
// 私有实例,volatile关键字,禁止指令重排。
private volatile static SingletonDemoInDoubleCheckLock instance;
// 私有构造
private SingletonDemoInDoubleCheckLock() {
}
// 公共获取实例方法(线程安全)
public static SingletonDemoInDoubleCheckLock getInstance() {
if(instance == null ) {
// 一重检查
synchronized (SingletonDemoInDoubleCheckLock.class) {
if(instance == null) {
// 二重检查
instance = new SingletonDemoInDoubleCheckLock();
}
}
}
return instance;
}
}
在加锁之前判断是否为空,可以确保 instance 不为空的情况下,不用加锁,可以直接返回。
加锁之后,还需要判断 instance 是否为空,是为了防止在多线程并发的情况下,会实例化多个对象。例如:线程 a 和线程 b 同时调用 getInstance 方法,假如同时判断 instance 都为空,这时会同时进行抢锁。假如线程 a 先抢到锁,开始执行 synchronized 关键字包含的代码,此时线程 b 处于等待状态。线程 a 创建完新实例了,释放锁了,此时线程 b 拿到锁,进入 synchronized 关键字包含的代码,如果没有再判断一次 instance 是否为空,则可能会重复创建实例。
双重检查锁:
- 双重判断,延迟加载;
- 线程安全;
- JDK 版本要求1.5起。
3.4 静态内部类
代码语言:javascript复制public class SingletonDemoInStaticInnerClass {
// 静态内部类
private static class InnerClass{
// 初始化实例
private final static SingletonDemoInStaticInnerClass INSTANCE = new SingletonDemoInStaticInnerClass();
}
// 私有构造
private SingletonDemoInStaticInnerClass() {
}
// 公关获取实例方法(线程安全,延迟加载)
public static SingletonDemoInStaticInnerClass getInstance() {
return InnerClass.INSTANCE;
}
}
静态内部类:
- 利用了
classloader
机制来保证初始化instance
时只有一个线程,线程安全; - 只有通过显式调用
getInstance
方法时,才会显式装载静态内部类,从而实例化instance
,延迟加载。
3.5 枚举
代码语言:javascript复制public enum SingletonEnum {
// 枚举元素本身就是单例
INSTANCE;
// 其他要执行的方法
public void sayHello() {
System.out.println("你好");
}
......
}
枚举:这是实现单例模式的最佳方法。它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。但是不是延迟加载的。
如何选用哪种方式实现单例模式? 一般情况下,不建议懒汉式,建议使用饿汉式;只有在要明确实现延迟加载效果时,才会使用静态内部类;如果涉及到反序列化创建对象时,可以尝试使用枚举;如果有其他特殊的需求,可以考虑使用双重检查锁。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/156328.html原文链接:https://javaforall.cn