Singleton:在 Java 编程中编写和使用的 6 种方法

简介: Singleton:在 Java 编程中编写和使用的 6 种方法

在  Java 编程中,类的对象创建或实例化是使用 “” 运算符和在类中声明的公共构造函数完成的,如下所示。new

Clazz clazz = new Clazz();

我们可以按如下方式读取代码片段:

Clazz()是使用 “new” 运算符调用的默认公共构造函数,用于创建或实例化类的对象并分配给变量 ,其类型为 。ClazzclazzClazz

在创建 单例时,我们必须确保只创建一个对象,或者只对一个类进行一次实例化。为确保这一点,以下常见事项成为先决条件。

所有构造函数都需要声明为 “” 构造函数。private

它防止在类外部创建带有“”运算符的对象。new

需要一个私有常量/变量对象持有器来保存单例对象;即,需要声明私有静态或私有静态最终类变量。

它保存单例对象。它充当单例对象的单一引用源

按照惯例,变量命名为 或 。INSTANCEinstance

需要允许其他对象访问单一实例对象的静态方法。

此静态方法也称为静态工厂方法,因为它控制类对象的创建。

按照惯例,该方法被命名为 。getInstance()

有了这种理解,让我们更深入地了解单例。以下是为类创建单例对象的 6 种方法。

1. 静态热切单例类

当我们手头有所有的实例属性,并且我们只想有一个对象和一个类来为一组相互关联的属性提供结构和行为时,我们可以使用静态热切单例类。这非常适合应用程序配置和应用程序属性。

public class EagerSingleton {
 
  private static final EagerSingleton INSTANCE = new EagerSingleton();
  private EagerSingleton() {}
     
  public static EagerSingleton getInstance() {
    return INSTANCE;
  }
  public static void main(String[] args) {
    EagerSingleton eagerSingleton = EagerSingleton.getInstance();
  }
}

在  JVM 中加载类本身时创建单例对象,并将其分配给常量。 提供对此常量的访问。INSTANCEgetInstance()

虽然编译时对属性 的依赖关系很好,但有时需要运行时依赖关系。在这种情况下,我们可以利用静态块来实例化单例。

public class EagerSingleton {
    private static EagerSingleton instance;
    private EagerSingleton(){}
 // static block executed during Class loading
    static {
        try {
            instance = new EagerSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Exception occurred in creating EagerSingleton instance");
        }
    }
    public static EagerSingleton getInstance() {
        return instance;
    }
}

单例对象是在 JVM 中加载类本身时创建的,因为所有静态块都是在加载时执行的。对变量的访问由 static 方法提供。instancegetInstance()

2. 动态懒惰单例类

Singleton 更适合应用程序配置和应用程序属性。考虑异构容器创建、对象池创建、层创建、外观创建、轻量级对象创建、每个请求的上下文准备和会话等:它们都需要动态构造单个对象,以便更好地“分离关注点”。在这种情况下,需要动态惰性单例。

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton(){}
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

仅当调用该方法时,才会创建单一实例对象。与静态热切单例类不同,此类不是线程安全的。getInstance()

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton(){}
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

该方法需要同步,以确保该方法在单例对象实例化中是线程安全的。getInstance()getInstance()

3. 动态懒惰改进的单例类

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton(){}
    public static LazySingleton getInstance() {
      if (instance == null) {
        synchronized (LazySingleton.class) {
            if (instance == null) {
                instance = new LazySingleton();
            }
        }
      }
      return instance;
    }
}

我们可以只通过双重检查或双重检查锁定来锁定块,而不是锁定整个方法,以提高性能和线程争用。getInstance()

public class EagerAndLazySingleton {
    private EagerAndLazySingleton(){}
    private static class SingletonHelper {
        private static final EagerAndLazySingleton INSTANCE = new EagerAndLazySingleton();
    }
    public static EagerAndLazySingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

仅当调用该方法时,才会创建单一实例对象。它是一个 Java 内存安全的单例类。它是一个线程安全的单例,并且是延迟加载的。它是使用最广泛和推荐的。getInstance()

尽管性能和安全性有所提高,但为类创建一个对象的唯一目标仍然受到 Java 中内存引用、反射和序列化的挑战。

内存参考:在多线程环境中,线程的读取和写入的重新排序可能会发生在引用的变量上,如果变量未声明为可变变量,则随时都可能发生脏对象读取。

反射:通过反射,可以公开私有构造函数,并可以创建新实例。

序列化:序列化实例对象可用于创建同一类的另一个实例。

所有这些都会影响静态和动态单例。为了克服这些挑战,它要求我们将实例持有者声明为易失性和覆盖,并且是 Java 中所有类的默认父类。equals()hashCode()readResolve()Object.class

4. 带枚举的单例

如果将枚举用于静态热切单例,则可以避免内存安全、反射和序列化问题。

public enum EnumSingleton {
    INSTANCE;
}

这些是伪装的静态渴望单例,线程安全。最好选择需要静态急切初始化的单例的枚举。

5. 具有函数和库的单例

虽然了解单例中的挑战和注意事项是必须理解的,但当人们可以利用经过验证的库时,为什么要担心反射、序列化、线程安全和内存安全呢?Guava 是一个非常流行且经过验证的库,它处理了许多编写有效 Java 程序的最佳实践。

我有幸使用  Guava 库来解释基于供应商的单例对象实例化,以避免大量繁重的代码行。将函数作为参数传递是 函数式编程的关键特性。虽然 supplier 函数提供了一种实例化对象生产者的方法,但在我们的例子中,生产者必须只生产一个对象,并且应该在单个实例化后重复返回相同的对象。我们可以记住/缓存创建的对象。使用 lambda 定义的函数通常被延迟调用以实例化对象,而记忆技术有助于延迟调用动态单例对象的创建。

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
public class SupplierSingleton {
    private SupplierSingleton() {}
    private static final Supplier<SupplierSingleton> singletonSupplier = Suppliers.memoize(()-> new SupplierSingleton());
    public static SupplierSingleton getInstance() {
        return singletonSupplier.get();
    }
    public static void main(String[] args) {
        SupplierSingleton supplierSingleton = SupplierSingleton.getInstance();
    }
}

函数式编程、供应商函数和记忆有助于使用缓存机制准备单例。当我们不想进行繁重的框架部署时,这是最有用的。

6. 带框架的单例:Spring、Guice

为什么还要担心通过供应商准备对象和维护缓存呢? 像 Spring 和 Guice 这样的框架在 POJO 对象上工作,以提供和维护单例。

这在企业开发中被大量使用,其中许多模块都需要自己的上下文和许多层。每个上下文和每个层都是单例模式的良好候选者。

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
class SingletonBean { }
@Configuration
public class SingletonBeanConfig {
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
    public SingletonBean singletonBean() {
        return new SingletonBean();
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SingletonBean.class);
        SingletonBean singletonBean = applicationContext.getBean(SingletonBean.class);
    }
}

Spring 是一个非常流行的框架。上下文和 依赖注入是 Spring 的核心。

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
interface ISingletonBean {}
class SingletonBean implements  ISingletonBean { }
public class SingletonBeanConfig extends AbstractModule {
    @Override
    protected void configure() {
        bind(ISingletonBean.class).to(SingletonBean.class);
    }
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new SingletonBeanConfig());
        SingletonBean singletonBean = injector.getInstance(SingletonBean.class);
    }
}

Google 的 Guice 也是一个准备单例对象的框架,也是 Spring 的替代品。

以下是在“单例工厂”中利用单例对象的方式。

Factory Method、Abstract Factory 和 Builders 与 JVM 中特定对象的创建和构造相关联。无论我们设想在哪里构建具有特定需求的对象,我们都可以发现单例的需求。可以查看和发现单例的其他地方如下。

原型或蝇量级

对象池

立 面

压条

上下文和类装入器

缓存

贯穿各领域的关切和面向方面的方案编制

当我们解决业务问题和非功能性需求约束(如性能、安全性以及 CPU 和内存限制)的用例时,就会出现模式。给定类的单例对象就是这样一种模式,它的使用要求将落到位以发现。该类本质上是创建多个对象的蓝图,但需要动态异构容器来准备“上下文”、“层”、“对象池”和“战略功能对象”,这确实促使我们利用声明全局可访问或上下文可访问的对象。


目录
相关文章
|
11天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
17 2
|
7天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
14天前
|
Java API Apache
Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
【10月更文挑战第29天】Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
68 5
|
9天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
91 53
|
8天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
5天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
7天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
19 2
|
8天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
11天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
50 4
|
8天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
36 1