关注:CodingTechWork,一起学习进步。
单例
介绍
单例模式(Singleton Pattern)是Java中最基础最简单的设计模式之一,这种模式属于创建型模式
,提供的就是一种创建对象的方式。这种模式中的单一类创建自己的对象,确保只有一个对象被创建,并且为所有对象提供一个访问它的全局访问点。
单例模式用来解决频繁创建与销毁实例对象问题,当我们想要控制实例创建个数或者复用实例时,就可以使用单例模式,这样有助于节省系统资源。
特点
- 单例类,顾名思义,只有一个实例。
- 单例类必须是自己创建自己的唯一实例。
- 单例类必须给所有对象提供这个唯一实例。
- 构造函数私有。
应用场景
适用范围
- 频繁的访问数据库或文件的对象。
- 频繁的需要实例化,然后销毁的对象。
- 创建对象耗时长且耗资源,但是又需要常用的对象。
使用举例
- Windows的任务管理器,每次只能打开一个。
- 应用程序的日志应用。
- 网上在线人数统计。
- 配置文件的访问类。
- 数据库的连接池。
- 多线程的线程池。
- 操作系统的文件系统。
设计思路
- 一个类每次都返回唯一的对象(每次返回都是同一个对象实例)。
- 提供一个获取该实例的方法。(基本上都是静态方法)
- 构造函数私有。
加载分类
-
提前加载
:在应用开始时就创建单例实例。 -
延迟加载
:在getInstance()方法
首次被调用时才调用单例创建,即需要使用时,才加载创建单例对象实例。
单例分类
1 饿汉式单例
介绍
- 类加载时就初始化好实例对象,容易产生垃圾对象,浪费内存。
- 无锁,执行效率高。
- 天生线程安全,因为类加载时就已初始化好实例。
代码
/**
* 饿汉式单例
* @author Andya
* @date 2021/3/9
*/
public class EHanSingleInstance {
//饿汉式:初始化时就建好实例
private static final EHanSingleInstance instance = new EHanSingleInstance();
private EHanSingleInstance() {
doSomething();
}
public static EHanSingleInstance getInstance() {
return instance;
}
public void doSomething() {
}
}
2 懒汉式单例
介绍
- 饿汉式属于延迟加载初始化。
- 若不加锁,则为非线程安全的。
- 若加锁,则为线程安全的,但效率很低,基本上每次都要同步。
无锁懒汉式代码
/**
* 无锁懒汉式单例,非线程安全
* @author Andya
* @date 2021/3/9
*/
public class LanHanNoLockSingleInstance {
//懒汉式:初始化时不创建实例,等需要时再创建
private static LanHanNoLockSingleInstance instance;
private LanHanNoLockSingleInstance() {
doSomething();
}
public static LanHanNoLockSingleInstance getInstance() {
//先判断是否为空,若为空创建一个新的实例
if (instance == null) {
instance = new LanHanNoLockSingleInstance();
}
return instance;
}
public void doSomething() {
}
}
加锁懒汉式代码
/**
* 加锁懒汉式单例,线程安全
* @author Andya
* @date 2021/3/9
*/
public class LanHanWithLockSingleInstance {
//懒汉式:初始化时不创建实例,等需要时再创建
private static LanHanWithLockSingleInstance instance;
private LanHanWithLockSingleInstance() {
doSomething();
}
public static synchronized LanHanWithLockSingleInstance getInstance() {
//先判断是否为空,若为空创建一个新的实例
if (instance == null) {
instance = new LanHanWithLockSingleInstance();
}
return instance;
}
public void doSomething() {
}
}
3 双重检锁式单例
介绍
在多线程应用中使用这种模式可以保证线程安全。因为如果实例为空,有可能存在两个线程同时调用getInstance()
方法的情况,这样的话,第一个线程会首先使用新构造器实例化一个单例对象,但此时它还没有完成单例对象的实例化操作,同时第二个线程也检查到单例实例为空,也会开始实例化单例对象,这就造成了2次实例化对象。所以多线程应用中,需要锁
来检查实例是否线程安全。
- 静态方法锁
public static synchronized Singleton getInstance()
- 代码块锁
synchronized(SingleInstance.class){
if (instance == null) {
instance = new SingleInstance();
}
}
虽然加锁可以保证线程安全,但是会带来延迟,因为加锁后,代码块在同一时刻只能被一个线程执行,但是同步锁只有在实例没被创建的时候才会起作用。如果单例实例已经被创建,其实不需要走该步骤。因此,我们可以在代码块锁外面再加一层实例空判断。instance == null
被检查2
次。
if (instance == null) {
synchronized(SingleInstance.class){
if (instance == null) {
instance = new SingleInstance();
}
}
}
代码
/**
* 双检锁单例
* @author Andya
* @date 2021/3/9
*/
public class DoubleCheckSingleInstance {
//使用volatile保证多线程的可见性
private static volatile DoubleCheckSingleInstance instance;
private DoubleCheckSingleInstance() {
doSomething();
}
public static DoubleCheckSingleInstance getInstance() {
//第一次检查是否创建过该单例
if (instance == null) {
//加锁,保证线程安全
synchronized (DoubleCheckSingleInstance.class) {
if (instance == null) {
instance = new DoubleCheckSingleInstance();
}
}
}
return instance;
}
public void doSomething() {
}
}
4 静态内部类单例
介绍
- 延迟加载初始化实例instance。
- 线程安全。
- 该方式只适用于静态域的情况。
- 该方式单例不会立即初始化,只有显式调用getInstance()方法时,才会显式转载静态内部类,才会实例化instance。
代码
/**
* 静态内部类单例
* @author Andya
* @date 2021/3/9
*/
public class StaticInternalSingleInstance {
//静态内部类
private static class StaticInternalSingleInstanceHolder{
private static final StaticInternalSingleInstance INSTANCE
= new StaticInternalSingleInstance();
}
private StaticInternalSingleInstance() {
doSomething();
}
public static final StaticInternalSingleInstance getInstance() {
return StaticInternalSingleInstanceHolder.INSTANCE;
}
public void doSomething() {
}
}
5 枚举类单例
介绍
- 不属于延迟加载初始化实例instance。
- 多线程安全。
- 支持序列化机制,从而防止多次实例化。
代码
/**
* 枚举型单例
* @author Andya
* @date 2021/3/9
*/
public enum EnumSingleInstance {
INSTANCE;
public EnumSingleInstance getInstance(){
return INSTANCE;
}
}
总结
一般在开发应用中,建议使用第1种饿汉式单例,而不推荐使用第2种懒汉式单例,除非明确需要延迟加载时,才会使用第4种静态内部类单例,若涉及到反序列化创建对象时,推荐使用第5种枚举式单例。其他也可以考虑使用第3种双重检锁式单例。
网友评论