单例模式
一、介绍
单例模式(Singleton Pattern)是设计模式中最简单,同时也是最常见的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。
二、特点
- 单一实例: 单例模式确保一个类只有一个实例对象。这个实例通常在首次访问时被创建,并在整个应用程序的生命周期内持续存在。
- 全局访问点: 单例模式提供一个全局的访问点,允许其他对象在程序中访问单例实例。这使得单例对象成为一个全局资源。
- 私有构造方法: 单例类通常会将其构造方法声明为私有,以防止外部直接实例化。这样,只有单例类内部能够控制实例的创建。
- 懒加载或饿汉式: 单例可以采用懒加载(Lazy Initialization)方式,在需要时才创建实例,或者采用饿汉式,在类加载时就创建实例。懒加载的方式能够节省资源,而饿汉式则保证了在多线程环境中的线程安全性。
- 线程安全性: 在多线程环境下,确保单例模式的线程安全性是关键。常见的实现方式包括使用双重检查锁定、静态内部类、枚举等机制来保证线程安全。
- 全局状态: 单例模式可能引入全局状态,这可能会对应用程序的可维护性和测试性产生影响。因此,在使用单例模式时,需要注意全局状态的管理。
- 可扩展性: 单例模式有助于将资源管理和控制封装在一个地方。这样,如果以后需要修改实例化逻辑,只需要修改单例类而不是整个代码库。
总体而言,单例模式适用于需要一个全局访问点,且在整个应用程序生命周期内只需要一个实例的情况。在设计中,需要权衡资源使用、线程安全性和全局状态等因素,选择适合特定需求的单例实现方式。
三、使用场景
- 资源共享: 当应用程序中的多个模块需要共享某个资源(例如配置信息、数据库连接、线程池等)时,可以使用单例模式确保全局只有一个实例,避免资源的浪费。
- 日志记录: 单例模式可以用于创建日志记录对象,以确保在整个应用程序中只有一个记录日志的实例,方便集中管理和控制日志输出。
- 工具类: 单例模式在创建工具类时非常有用,例如日志管理、配置管理等。通过单例模式,可以将这些工具类的实例化过程封装在一个类中,提供全局的访问点。
- spring容器:spring容器管理的bean都是单例的
四、实现方式
4.1 懒汉式
- 优点:在使用的时候才进行加载
- 缺点:如果不加锁会出现线程安全问题,加锁第一次加载的时候如果请求量比较大会进行阻塞
主要讲解常用的一些实现,如登记式、枚举等可自行了解
4.1.1 线程不安全
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程,严格意义上它并不算单例模式。
package com.lcy.study.design.singleton;
/**
* 单例模式懒汉加载-非线程安全
* @Author lcy
* @Date 2021/7/19 14:11
*/
public class SingletonLazyNoSafe {
/**
* 私有静态变量
*/
private static SingletonLazyNoSafe singletonLazy;
private SingletonLazyNoSafe(){}
/**
* 公有方法,提供全局访问点
* @return com.lcy.study.design.singleton.SingletonLazy
* @author lcy
* @date 2021/7/19 14:18
**/
public static SingletonLazyNoSafe newInstance(){
if (singletonLazy == null) {
singletonLazy = new SingletonLazyNoSafe();
}
return singletonLazy;
}
}
4.1.2 线程安全-全局锁
线程安全通过synchronized锁保证,但是锁的颗粒度大
package com.lcy.study.design.singleton;
/**
* 单例模式懒汉加载-线程安全synchronized锁
* @Author lcy
* @Date 2021/7/19 14:11
*/
public class SingletonLazySync {
/**
* 私有静态变量
*/
private static SingletonLazySync singletonLazy;
private SingletonLazySync(){}
/**
* 通过加锁保证单例。但是锁颗粒度大
* @return com.lcy.study.design.singleton.SingletonLazy
* @author lcy
* @date 2021/7/19 14:18
**/
public synchronized static SingletonLazySync newInstance(){
if (singletonLazy == null) {
singletonLazy = new SingletonLazySync();
}
return singletonLazy;
}
}
4.1.3 线程安全-double check(推荐)
全局锁由于锁的颗粒度较大,每次在获取单例对象的时候会获取一次锁,影响性能,这里减少锁的颗粒度,提高效率。
注意:现在高版本的Java(JDK9) 已经在JDK 内部实现中解决了这个问题,把对象的new 操作和初始化操作设计为原子操作。jdk9及以后得版本是不需要加volatile修饰变量。
package com.lcy.study.design.singleton;
/**
* 单例模式线程安全-double check
* @Author lcy
* @Date 2021/7/19 14:11
*/
public class SingletonLazy {
/**
* 私有静态变量,用于存储唯一实例,加上volatile禁止jvm指令重排
*/
private volatile static SingletonLazy singletonLazy;
private SingletonLazy(){}
/**
* 公有方法,提供全局访问点 通过加锁保证单例,减少锁颗粒度
* <p>
* 使用DCL(double-checked-locking) 双重检查
* <p>
* 使用volatile防止jvm指令重排,原因: new 对象正常的顺序为 1、分配空间给对象 2、分配内存地址给对象 3、将对象赋值给引用instance
* <p>
* @return com.lcy.study.design.singleton.SingletonLazy
* @author lcy
* @date 2021/7/19 14:18
**/
public static SingletonLazy newInstance(){
if (singletonLazy == null) {
//多线程的情况下可能会出现多个线程进入到这里等待锁,里层需要再判断null
synchronized (SingletonLazy.class) {
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
}
}
return singletonLazy;
}
}
4.2 饿汉加载
饿汉式实现相对简单,但是比较浪费资源,取决于具体使用的场景,比如spring容器启动的时候加载的bean默认就是饿汉式
- 优点:实现简单,没有线程安全问题
- 缺点:对象在没有被使用的时候始终大量占用内存
package com.lcy.study.design.singleton;
/**
* 单例模式饿汉加载
* @Author lcy
* @Date 2021/7/19 14:11
*/
public class SingletonHungry {
/**
* 单例对象
*/
private static final SingletonHungry singletonLazy = new SingletonHungry();
private SingletonHungry(){}
/**
* 获取单例对象
* @return com.lcy.study.design.singleton.SingletonLazy
* @author lcy
* @date 2021/7/19 14:18
**/
public static SingletonHungry newInstance(){
return singletonLazy;
}
}