当前位置:Java -> 单例模式: 在Java编程中的六种实现方式与使用方法

单例模式: 在Java编程中的六种实现方式与使用方法

Java编程中,对象的创建或类的实例化是通过"new"操作符和在类中声明的公共构造函数来完成的。如下:

Clazz clazz = new Clazz();


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

Clazz()是默认的公共构造函数,通过"new"操作符调用为Clazz类创建或实例化一个对象,并将其分配给变量clazz,其类型为Clazz

在创建singleton时,我们必须确保只创建一个对象或只发生一次类的实例化。为了确保这一点,以下共同事项成为先决条件。

  1. 所有构造函数需要声明为"private"构造函数。
    • 它防止在类外部使用"new"操作符创建对象。
  2. 需要一个私有常量/变量对象持有者来持有singleton对象;即需声明一个私有静态或私有静态final类变量。
    • 它持有singleton对象。它充当singleton对象的单一引用源
    • 按照惯例,变量被命名为INSTANCEinstance
  3. 需要一个静态方法来允许其他对象访问singleton对象。
    • 此静态方法也被称为静态工厂方法,因为它控制类的对象创建。
    • 按照惯例,该方法被命名为getInstance()

1. 静态急切模式的Singleton类

当我们手头上有所有实例属性,并且我们只希望有一个对象和一个类为一组相关属性提供结构和行为时,我们可以使用静态急切模式的singleton类。这非常适合应用程序配置和应用程序属性。

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();
  }
}


singleton对象是在JVM加载类本身时创建的,并分配给INSTANCE常量。getInstance()提供对该常量的访问。

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

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;
    }
}


singleton对象是在JVM加载类本身时创建的,因为所有静态块在加载时执行。通过getInstance()静态方法提供对instance变量的访问。

2. 动态延迟模式的Singleton类

singleton更适合应用程序配置和应用程序属性。考虑异构容器创建、对象池创建、层创建、外观创建、享元对象创建、为每个请求和会话准备上下文等:它们都需要更好的“关注分离”的动态构建singleton对象。

public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

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

 

singleton对象只有在调用getInstance()方法时才会创建。与静态快速完成的singleton类不同,该类不是线程安全的。

public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

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

}


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

3. 动态延迟改进模式的Singleton类

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;
    }
}


singleton对象只有在调用getInstance()方法时才会创建。它是一个Java内存安全的singleton类。它是一个线程安全的singleton,延迟加载的。这是最常用的和推荐的方法。

内存引用、反射和序列化挑战。
  • 内存引用:在多线程环境中,对于引用的变量可以对线程的读写重新排序,如果变量没有声明为volatile,则随时可以发生脏对象读取。
  • 反射:通过反射,私有构造函数可以变为公共,从而可以创建一个新实例。
  • 序列化:序列化实例对象可用于创建同一类的另一个实例。
Object.class的equals()hashCode()readResolve()

4. 使用枚举创建Singleton

public enum EnumSingleton {
    INSTANCE;
}


5. 使用函数和库创建Singleton

要理解单例模式中的挑战和注意事项是必须的,那么为什么要担心反射、序列化、线程安全和内存安全呢,当可以利用已被证实的库呢? Guava 就是这样一个受欢迎且经受过考验的库,处理许多编写高效Java程序的最佳实践。

我有幸使用 Guava库 来解释基于supplier的单例对象实例化,避免编写大量繁重的代码。将函数作为参数传递 是 函数式编程 的关键特性。 在我们的例子中,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();
    }
}


函数式编程、supplier函数和记忆化有助于准备带有缓存机制的单例。当我们不想要进行繁重的框架部署时,这是非常有用的。

6. 使用框架创建单例:Spring、Guice

甚至准备通过supplier获取对象并维护缓存都要担心吗? 像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的替代框架。

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

  • 工厂方法、抽象工厂和构造者与在JVM中创建和构建特定对象相关。无论我们在何处设想使用特定需求构造对象,都可以发现单例的需求。进一步的地方可以检查和发现单例的地方如下。
  1. 原型或享元
  2. 对象池
  3. 外观
  4. 分层
  5. 上下文和类加载器
  6. 缓存
  7. 横切关注点和面向切面编程

结论

模式出现是因为我们解决业务问题和非功能性需求限制(如性能、安全性、CPU和内存限制)时。对于给定类的单例对象是这样一个模式,其使用需求将在此发现倒计时中。该类本质上是创建多个对象的蓝图,但动态异构容器准备“上下文”、“层次”、“对象池”和“策略功能对象”的需要推动我们利用声明全局访问或上下文访问的对象。

感谢您宝贵的时间,希望您找到了有用的东西可以重新访问和发现。

推荐阅读: 27.Redis高可用方案如何实现?

本文链接: 单例模式: 在Java编程中的六种实现方式与使用方法