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 和内存限制)的用例时,就会出现模式。给定类的单例对象就是这样一种模式,它的使用要求将落到位以发现。该类本质上是创建多个对象的蓝图,但需要动态异构容器来准备“上下文”、“层”、“对象池”和“战略功能对象”,这确实促使我们利用声明全局可访问或上下文可访问的对象。


目录
相关文章
|
29天前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
21天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
1天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
5天前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
32 12
|
1天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
10 2
|
24天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
24天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
18天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
18天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
42 3
|
28天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界里,异常是程序运行中不可忽视的“惊喜”。它们可能突如其来,也可能悄无声息地潜伏。掌握异常处理的艺术,意味着你能够优雅地面对程序的不完美,并确保它即使在风雨飘摇中也能继续航行。本文将引导你理解Java异常的本质,探索捕获和处理这些异常的方法,并最终学会如何利用自定义异常为你的代码增添力量。