深度学习设计模式之单例模式
深度学习设计模式之单例模式
单例模式是软件设计模式中最基础且应用最广泛的一种,特别是在深度学习等需要高效资源管理的场景中。本文将深入探讨单例模式的实现方式、优缺点以及在实际开发中的应用,帮助读者全面理解这一重要设计模式。
一、单例模式简介
单例模式确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要控制资源访问和管理的场景中特别有用,例如数据库连接池、配置文件管理器和缓存系统等。
二、单例模式实现步骤
单例模式的实现通常包括以下几个关键步骤:
- 使用一个私有构造函数,防止外部直接创建实例
- 定义一个私有静态变量来保存唯一的实例
- 提供一个公有静态方法来获取该实例
通过这些步骤,可以确保类的实例在程序运行期间只被创建一次。
三、单例模式的两种方式
1.懒汉模式
懒汉模式的核心思想是“按需创建”,即只有在真正需要对象时才进行实例化。这种方式可以节省资源,但如果处理不当,在多线程环境下可能会导致多个实例的创建。
1.1 简易版懒汉模式
public class LazySingleton {
/**
* 成员变量
*/
private static LazySingleton instance;
/**
* 构造方法私有化
*/
private LazySingleton(){
}
/**
* 获取单例对象
* @return
*/
public static LazySingleton getInstance(){
if(instance == null){
System.out.println("创建实例");
instance = new LazySingleton();
return instance;
}
System.out.println("实例对象已存在,无需再创建");
return instance;
}
}
测试类
public static void main(String[] args) {
// 先创建一个对象,看是否有输出
LazySingleton instance = LazySingleton.getInstance();
LazySingleton instance1 = LazySingleton.getInstance();
}
结果:简易版的单例模式在多线程环境下存在线程安全问题,可能会创建多个实例。
1.2 线程安全的单例模式
为了解决多线程问题,可以在getInstance()
方法上添加synchronized
关键字。
public class LazySingleton {
/**
* 成员变量
*/
private static LazySingleton instance;
/**
* 构造方法私有化
*/
private LazySingleton(){
}
/**
* 获取单例对象
* @return
*/
public static synchronized LazySingleton getInstance(){
if(instance == null){
System.out.println("创建实例");
instance = new LazySingleton();
return instance;
}
System.out.println("实例对象已存在,无需再创建");
return instance;
}
}
这种方法虽然解决了多线程问题,但在性能上有所损失,因为每次调用getInstance()
都会进行同步。
1.3 线程安全的单例模式V2.0版
为了优化性能,可以将synchronized
关键字应用到代码块中,仅在创建实例时进行同步。
public class LazySingleton {
/**
* 成员变量
*/
private static LazySingleton instance;
/**
* 构造方法私有化
*/
private LazySingleton(){
}
/**
* 获取单例对象
* @return
*/
public static LazySingleton getInstance(){
if(instance == null){
synchronized (LazySingleton.class){
System.out.println("创建实例");
instance = new LazySingleton();
}
return instance;
}
System.out.println("实例对象已存在,无需再创建");
return instance;
}
}
这种方式虽然减少了锁的粒度,但在某些情况下仍可能创建多个实例。
1.3 线程安全的单例模式V2.1版-双重校验锁
通过增加双重检查机制,可以进一步优化线程安全问题。
public class LazySingleton {
/**
* 成员变量
*/
private static LazySingleton instance;
/**
* 构造方法私有化
*/
private LazySingleton(){
}
/**
* 获取单例对象
* @return
*/
public static LazySingleton getInstance(){
if(instance == null){
synchronized (LazySingleton.class){
if(instance == null){
System.out.println("创建实例");
instance = new LazySingleton();
}
}
return instance;
}
System.out.println("实例对象已存在,无需再创建");
return instance;
}
}
1.3 线程安全的单例模式V3.0版-双重校验锁终极版本
为了解决指令重排序问题,可以使用volatile
关键字修饰实例变量。
public class LazySingleton {
/**
* 成员变量
*/
private static volatile LazySingleton instance;
/**
* 构造方法私有化
*/
private LazySingleton(){
}
/**
* 获取单例对象
* @return
*/
public static LazySingleton getInstance(){
if(instance == null){
synchronized (LazySingleton.class){
if(instance == null){
System.out.println("创建实例");
instance = new LazySingleton();
}
}
return instance;
}
System.out.println("实例对象已存在,无需再创建");
return instance;
}
}
2.饿汉模式
饿汉模式在类加载时就创建实例,简单且线程安全,但可能会造成资源浪费。
public class HungrySingleton {
// 一开始就初始化对象
private static HungrySingleton instance = new HungrySingleton();
// 私有化构造方法
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
四、扩展实现单例
1.使用枚举的方式实现单例模式
使用枚举可以避免反射等特殊情况下创建多个实例的问题。
public enum SingletonEnum {
INSTANCE;
public void test(){
System.out.println("1111");
}
}
2.使用内部类的方式实现单例模式
内部类方式结合了懒加载和线程安全的优点。
/**
* 静态内部类方式实现单例
*/
public class Singleton {
// 私有化构造方法
private Singleton(){}
public void test(){
System.out.println("2222");
}
/**
* 静态内部类
*/
private static class SingletonHolder{
// 初始化对象
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
FAQ
1.为什么要私有化构造方法?
单例模式的核心是保证对象只被实例化一次,因此需要将构造方法私有化,防止外部通过new
关键字创建实例。
2.为什么成员变量要用 static 修饰?
由于构造方法被私有化,无法通过实例化对象来调用方法,因此需要使用静态方法来访问成员变量。为了使静态方法能够访问成员变量,需要将成员变量声明为静态。
3.单例模式的应用场景?
- 数据库连接池:确保应用程序中只有一个数据库连接池实例,避免资源浪费。
- 配置文件管理器:确保在整个应用程序中只有一个配置文件管理器实例。
- 缓存系统:确保只有一个缓存实例,提高性能。
4.单例模式使用的注意情况
单例模式主要分为懒汉模式和饿汉模式。在选择使用哪种模式时,需要综合考虑资源占用和使用频率等因素。对于占用内存小的类,可以使用饿汉模式;对于占用内存较大的类,则更适合使用懒汉模式。
5.JDK中的单例
JDK中的一些类也使用了单例模式。例如,java.lang.Runtime
类使用的是饿汉模式,而java.awt.Desktop
类则使用了懒汉模式。