JAVA - 单例设计模式
@[TOC](JAVA - 单例设计模式)
前言
这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱
一. 简介
单例模式(Singleton Pattern的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
二. 单例模式的原则
- 单例模式限制类的实例化,并确保Java虚拟机中只存在该类的一个实例。
- 单例类必须提供一个全局访问点来获取该类的实例。
- 单例模式用于日志记录、驱动对象、缓存和线程池。
- 单例设计模式还用于其他设计模式,如抽象工厂等。
- 单例设计模式也用在核心 Java 类中(例如,
java.lang.Runtime
、java.awt.Desktop
)。
三. 单例模式的实现
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
简单理解:在自己身体里面创建自己,给外部调用
1.1 饿汉式
1.1.1 静态变量初始化方式
在系统初始化时候,单例类的实例是在类加载时创建的。静态变量方式初始化的缺点是,即使客户端应用程序可能没有使用该方法,也会创建该方法。这是静态初始化单例类的实现:
/**
* @author: 那就叫小智吧
* @date: 2022/3/3 15:41
* @Description: 静态变量创建类对象
* 该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
*/
public class Singleton1 {
// 私有构造方法
private Singleton1(){
System.out.println("通过静态变量创建类对象");
}
// 在成员位置创建该类的对象
private static Singleton1 instance= new Singleton1();
// 对外提供静态方法获取该对象
public static Singleton1 getInstance(){
return instance;
}
}
如果您的单例类没有使用大量资源,则可以使用这种方法。但在大多数情况下,单例类是为文件系统、数据库连接等资源创建的。除非客户端调用该
getInstance
方法,否则我们应该避免实例化。此外,此方法不提供任何异常处理选项。
1.1.2 静态代码块初始化方式
静态代码块方式实现与静态变量初始化方式类似,不同之处在于类的实例是在提供异常处理选项的静态块中创建的。
/**
* @author: 那就叫小智吧
* @date: 2022/3/3 15:49
* @Description: 在静态代码块中创建该类对象
* 该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。当然该方式也存在内存浪费问题。
*/
public class Singleton2 {
// 私有构造方法
private Singleton2(){
System.out.println("在静态代码块中创建该类对象");
};
// 在成员位置声明静态变量
private static Singleton2 instance;
static {
try {
instance = new Singleton2();
} catch (Exception e) {
throw new RuntimeException("创建单例实例时发生异常");
}
}
// 对外提供静态方法获取该对象
public static Singleton2 getInstance(){
return instance;
}
}
静态代码块方式实现与静态变量初始化方式 都会在使用实例之前创建实例,但这不是最佳实践。
1.1.3 枚举方式
/**
* @author: 那就叫小智吧
* @date: 2022/3/3 16:36
* @Description: 恶汉式
* 枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
*/
public enum Singleton {
INSTANCE;
}
使用
enum
Java 来实现单例设计模式,以确保任何enum
值在 Java 程序中仅实例化一次。由于Java 枚举值是全局可访问的,因此单例也是如此。缺点是enum
类型有些不灵活(例如,它不允许延迟初始化)。
1.2 懒汉式
1.2.1 懒加载初始化方法 (线程不安全)
/**
* @author: 那就叫小智吧
* @date: 2022/3/3 15:59
* @Description: 懒汉式 :线程不安全
* 从下面面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,
* 那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。
*/
public class Singleton1 {
// 构造私有方法
private Singleton1(){
System.out.println("懒汉式:线程不安全");
}
// 在成员位置声明静态变量
private static Singleton1 instance;
// 对外提供静态方法获取改对象
public static Singleton1 getInstance(){
if (instance == null){
instance = new Singleton1();
}
return instance;
}
}
这种实现在单线程环境中工作得很好,但是当涉及到多线程系统时,如果多个线程
if
同时处于该条件内,则可能会导致问题。它将破坏单例模式,并且两个线程将获得单例类的不同实例。
1.2.2 懒加载初始化方法 (线程安全)
创建线程安全单例类的一种简单方法是使全局访问方法同步,以便一次只有一个线程可以执行该方法。以下是此方法的一般实现:
/**
* @author: 那就叫小智吧
* @date: 2022/3/3 16:04
* @Description: 懒汉式 : 线程安全
* 该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。
*/
public class Singleton2 {
// 私有构造方法
private Singleton2(){};
// 在成员位置声明静态变量
private static Singleton2 instance;
// 对外提供静态方法获取对象
public static synchronized Singleton2 getInstance(){
if (instance != null) {
instance = new Singleton2();
}
return instance;
}
}
1.2.3 双重检查锁
前面的实现,能够正常运行并且提供了线程安全性,但是由于与同步方法相关的成本,它降低了性能,尽管我们只需要它用于可能创建单独实例的前几个线程。为了避免每次都产生额外的开销,使用了双重检查锁定原则。
/**
* @author: 那就叫小智吧
* @date: 2022/3/3 16:08
* @Description: 懒汉式 :双重检查方式
* 对于 `getInstance()` 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
*/
public class Singleton3 {
// 私有构造方法
private Singleton3() {
System.out.println("懒汉式 : 双重检查方式");
}
// 在成员位置声明静态变量
private static Singleton3 instance;
// 对外提供静态方法获取该对象
public static Singleton3 getInstance() {
// 第一次判断,如果instance不为null,不进入枪锁阶段,直接返回实例
if (instance == null) {
synchronized (Singleton3.class) {
// 抢到锁之后再次判断是否为null
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
}
检查锁模式带来空指针异常的问题
/**
* @author: 那就叫小智吧
* @date: 2022/3/3 16:25
* @Description: 懒汉式: 双重检查方式
* 双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
*
* 要解决双重检查锁模式带来空指针异常的问题,只需要使用 `volatile` 关键字, `volatile` 关键字可以保证可见性和有序性。
*/
public class Singleton4 {
// 私有构造方法
private Singleton4() {}
private static volatile Singleton4 instance;
// 对外提供静态方法获取该对象
public static Singleton4 getInstance() {
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
if(instance == null) {
synchronized (Singleton4.class) {
//抢到锁之后再次判断是否为空
if(instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}
}
添加 volatile
关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。
1.2.4 静态内部类方式
/**
* @author: 那就叫小智吧
* @date: 2022/3/3 16:29
* @Description: 懒汉式 : 静态内部类方式
* 静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 `static` 修饰,保证只被实例化一次,并且严格保证实例化顺序。
*/
public class Singleton5 {
// 私有构造方法
private Singleton5() {}
// 创建静态内部类
private static class SingletonHolder {
private static final Singleton5 instance = new Singleton5();
}
//对外提供静态方法获取该对象
public static Singleton5 getInstance() {
return SingletonHolder.instance;
}
}
/**
第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
*/
作者:神的孩子都在歌唱
本人博客:https://blog.csdn.net/weixin_46654114
转载说明:务必注明来源,附带本人博客连接。
- 点赞
- 收藏
- 关注作者
评论(0)