欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 能源 > 单例模式的实现

单例模式的实现

2024/10/25 21:23:12 来源:https://blog.csdn.net/weixin_44073008/article/details/140223316  浏览:    关键词:单例模式的实现

1. 引言

1.1 背景

当在应用程序中需要控制资源共享、进行配置管理和日志记录等操作时,一种常见的需求是希望通过一个全局访问点,让程序无论在哪个地方,只要能够访问到,就可以通过这个全局访问点,来获取相关实例信息。为满足这种需求,我们可以采用单例模式(Singleton Pattern)。单例模式确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

具体来说,单例模式通常会提供一个静态方法(例如getInstance()),这个方法返回类的唯一实例。由于这个方法是静态的,因此它可以在不创建类实例的情况下被调用。这意味着任何代码只要能够访问到该类,就可以通过调用这个静态方法来获取单例实例。

1.2 目的

本文将详细介绍单例模式的基本概念、实现步骤。通过本篇文章,你将能够理解单例模式的工作原理,并学会如何在实际项目中有效地利用它。

2. 何为单例模式?

讲个趣味性点的例子,单例模式就像是一个动漫世界里的主角光环,无论剧情如何发展,主角永远只有一个,而且每个人都知道他是故事的核心。这样,无论故事如何展开,大家都能找到同一个人来推动剧情。

2.1 单例模式的优缺点

优点

确保单一实例:避免重复创建实例,节省资源。
全局访问点:方便全局访问,简化调用。
延迟初始化:按需创建实例,提高性能。

缺点

难以扩展:单例类通常难以扩展,因为构造函数是私有的。
潜在的性能问题:在高并发环境下,某些实现方式可能会有性能问题。
测试困难:单例模式可能会导致测试困难,因为它是全局状态。

2.2 单例模式的使用场景

根据单例模式的特点,它的使用场景可以分为如下几个:

  • 比如说在资源共享的情况下,可以将配置文件数据、日志文件放在一个文件中,这些配置数据或日志文件由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这样可以简化在复杂环境下的配置管理。
  • 在控制资源的情况下,比如说线程池中,多线程的线程池的设计一般采用单例模式,方便对池中的线程进行控制。

3. 单例模式的实现模式

单例模式的实现通常包括三个要素:

  1. 私有构造方法,将类的构造函数设为私有,这样外部就无法通过 new 关键字来创建实例。
  2. 私有静态引用指向自己实例,在类内部创建一个静态的实例变量,用于保存唯一的实例。
  3. 以自己实例为返回值的公有静态方法,提供一个静态方法,让外部可以通过这个方法获取到唯一的实例。

3.1 饿汉式单例模式

对于饿汉式单例模式,单例实例在类装载时就构建,线程安全,因为在类加载的同时已经创建好一个静态对象,调用时反应速度快。缺点也很明显,资源效率不高,只要执行该类的其他静态方法或者加载了该类,这个实例仍然会初始化。

/**    * 饿汉单例模式:在还没有实例化的时候就初始化*/
public class Hungry {    //1. 开始时就创建实例private static final Hungry instance=new Hungry();// 2. 私有化的构造方法private void hungry() {  }public static Hungry getInstance() { // 返回单例名return instance;  		}
}

3.2 懒汉式单例模式

对于懒汉式单例模式,单例实例在第一次被使用时构建,延迟初始化,相对资源利用率高。缺点是当多个线程同时访问就可能同时创建多个实例,而这多个实例不是同一个对象,虽然后面创建的实例会覆盖先创建的实例,但还是会存在拿到不同对象的情况。

/*** 懒汉单例模式:没有用就不初始化,要用时,才初始化*/
public class Slacker {// 1. 静态属性,存储单例private static Slacker instance = null;  // 2. 私有的构造器,限制外部不能访问private void slacker() {   }// 3. 静态方法,获取单例public static Slacker getInstance() {  if (instance == null) {// 初始化instance = new Slacker();}// 4. 返回单例名return instance;  }
}

3.3 双重检测懒汉式单例模式

双重检测懒汉式单例模式,就是为了解决懒汉式单例模式的缺点的,使用了synchronized关键字对实例初始化前后进行加锁。缺点是第一次加载时反应不快,多线程使用不必要的同步开销大。

/*** 双重检查锁定的懒汉模式*/
public class LockUp {// 1. 静态属性,存储单例private static volatile LockUp instance = null;    // 2. 私有的构造器,限制外部不能访问private LockUp() {}// 3. 静态方法,获取单例public static LockUp getInstance() {    if (instance == null) {// 加锁保证一次运行一个synchronized (LockUp.class) {if (instance == null) {instance = new LockUp();}}}return instance;    }    
}

3.4 静态内部类

静态内部类,资源利用率高,单例实例在第一次使用时构建,延迟初始化。缺点是第一次加载时反应不够快。

public class Singleton {private Singleton() {// 初始化代码}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

3.5 枚举单例模式

利用枚举实现单例,在还没有实例化的时候就初始化,简洁且线程安全。

public enum Singleton {INSTANCE;// 添加需要的属性和方法public void someMethod() {// 方法实现}
}

4. 单例模式的具体实现流程

4.1 如何使用单例模式管理配置文件数据?

  1. 首先,基于SpringBoot项目,假设有配置文件application.properties:
db.host=localhost
db.port=3306
  1. 接着,使用单例模式,管理配置文件
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;public class ConfigurationManager {// 1. 静态实例,存储单例private static ConfigurationManager instance;// 2. 配置属性,用于加载配置文件private Properties properties;// 3. 私有的构造器,限制外部不能访问private ConfigurationManager() {properties = new Properties();try {// 读取配置文件FileInputStream fileInputStream = new FileInputStream("application.properties");properties.load(fileInputStream);fileInputStream.close();} catch (IOException e) {e.printStackTrace();}}// 4. 静态方法,获取单例public static synchronized ConfigurationManager getInstance() {if (instance == null) {instance = new ConfigurationManager();}return instance;}// 6. 公共方法,提供获取配置文件属性的功能public String getProperty(String key) {return properties.getProperty(key);} 
}
  1. 通过ConfigurationManager.getInstance() 获取单例实例
public static void main(String[] args) {// 获取单例实例ConfigurationManager configManager = ConfigurationManager.getInstance();// 获取配置信息String dbHost = configManager.getProperty("db.host");String dbPort = configManager.getProperty("db.port");System.out.println(dbHost + ":" + dbPort);}

4.2 如何使用单例模式实现线程池?

在多线程环境中,线程池通常采用单例模式来确保全局只有一个线程池实例,从而方便对池中的线程进行控制和管理。

  1. 首先依旧是构建一个单例类,用于管理线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolManager {// 1. 静态实例,存储单例private static ThreadPoolManager instance;// 2. 设置线程池private ExecutorService executorService;// 3. 使用私有的构造器,限制外部不能访问private ThreadPoolManager() {// 创建一个固定大小的线程池executorService = Executors.newFixedThreadPool(10);}// 4. 静态方法,获取单例public static synchronized ThreadPoolManager getInstance() {if (instance == null) {instance = new ThreadPoolManager();}return instance;}// 5. 公共方法,提供提交任务到线程池的功能public void submitTask(Runnable task) {executorService.submit(task);}// 6. 公共方法,提供关闭线程池的功能public void shutdown() {executorService.shutdown();}
}
  1. 通过ThreadPoolManager.getInstance() 获取单例实例
public static void main(String[] args) {// 获取单例线程池实例ThreadPoolManager threadPoolManager = ThreadPoolManager.getInstance();// 提交任务到线程池,假设有10个任务for (int i = 0; i < 10; i++) {final int taskNumber = i;threadPoolManager.submitTask(() -> {System.out.println("当前编号为" + taskNumber + "的线程名称是:" + Thread.currentThread().getName());});}// 关闭线程池threadPoolManager.shutdown();
}

4.3 Spring Bean的单例模式管理配置文件数据

如果学过Spring的小伙伴,应该清楚,Spring框架中,默认情况下管理的Bean是单例的,这也意味着Spring容器在创建和管理Bean时,每个Bean只会有一个实例,并且这个实例会被所有需要它的地方共享。例如

import org.springframework.stereotype.Component;@Component
public class SingletonBean {// Bean的实现
}

依旧是举个示例:使用Spring Bean的单例模式获取配置文件中,数据库的基本信息。

  1. 首先,配置文件信息还是假设application.properties :
db.host=localhost
db.port=3306
  1. 创建一个Java类绑定配置属性
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;@Component
@ConfigurationProperties(prefix = "db")
public class DbConfig {private String host;private String port;
}
  1. 在Spring Boot应用的启动类绑定配置属性
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;@SpringBootApplication
@EnableConfigurationProperties(DbConfig.class)
public class MyAppApplication {public static void main(String[] args) {SpringApplication.run(MyAppApplication.class, args);}
}
  1. 最后,只需要注入DbConfig Bean,就可以使用这些配置属性
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class SingletonService {@Autowiredprivate DbConfig dbConfig;public void getProperty() {System.out.println(dbHost + ":" + dbPort);}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com