【设计模式】-创建型模式-第2章第1讲-【单例模式】

简介: 【设计模式】-创建型模式-第2章第1讲-【单例模式】

本章主要介绍创建型模式(Creational Pattern)。创建型模式主要用于处理对象的创建问题。

目录

1、单例模式

1.1、为何要用单例模式

1.2、单例模式(singleton pattern)使我们常用的设计模式。单例,顾名思义,用来保证一个对象只能创建一个实例,并且提供对实例的全局访问方法。

1.3、同步锁单例模式

1.3.1、获取实例的方法 getInstance() 上添加 synchronized 关键字来保证线程安全。

1.3.2、用 synchronized 代码块包装 if(instance==null) 条件,这里需要指定一个对象来提供锁,Singleton.class 对象就起这种作用。

1.4、双重检验锁机制的同步锁单例模式

1.5、无锁的线程安全单例模式

1.6、提前加载和延迟加载

1.7、我们所熟知的单例模式的应用

1.7.1、枚举类实现单例模式

1.7.2、Jdk 源码中单例模式

1.7.3、spring 源码中单例模式

单例模式
工厂模式
建造者模式
原型模式
对象池模式

1、单例模式
1.1、为何要用单例模式

单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。

在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例 
单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。 
提供了对唯一实例的受控访问。 
由于在系统内存中只存在一个对象,因此可以 节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。 
允许可变数目的实例。 
避免对共享资源的多重占用。

1.2、单例模式(singleton pattern)使我们常用的设计模式。单例,顾名思义,用来保证一个对象只能创建一个实例,并且提供对实例的全局访问方法。

通俗点就是:私有化构造器,提供全局静态访问方法。

示例代码如下:

package com.zhaoyanfei.designpattern.signleton;
/**
 * 单例模式
 * @author zhaoYanFei
 *
 */
public class Singleton {
 
    private static Singleton instance;
    /**
     * 私有构造器
     */
    private Singleton() {
        System.out.println("Singleton is Instantiated.");
    }
    /**
     * 全局静态方法访问实例
     * @return
     */
    public static Singleton getInstance() {
        if(instance == null) 
            instance = new Singleton();
        return instance;
    }
    
    public void doSomething() {
        System.out.println("Something is done.");
    }
}

接下来,当我们在代码中使用单例对象时,只需进行简单调用,示例如下:

package com.zhaoyanfei.designpattern.signleton;
 
public class Test {
 
    public static void main(String[] args) {
        //使用单例对象执行相应的操作
        Singleton.getInstance().doSomething();
    }
    
}

1.3、同步锁单例模式

单例模式的实现代码简单且高效,但是还需要注意一种特殊情况,那就是多线程应用中,可能存在两个线程同时调用 getInstance 方法的情况。
比如:第一个线程首先使用构造器实例化单例对象,这时,第二个线程进来,在第一个线程还完成单例对象的实例化操作的情况下,也会开始实例化单例对象。

当然,上述场景发生的概率很小,但是在实例化单例对象需要较长时间的情况下,发生的可能性就足够高了,所以,我们不能忽略这种并发操作。

解决这个问题也很简单,只需要用关键字(synchronized)来保证线程安全。有两种方式:
1.3.1、获取实例的方法 getInstance() 上添加 synchronized 关键字来保证线程安全。

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

1.3.2、用 synchronized 代码块包装 if(instance==null) 条件,这里需要指定一个对象来提供锁,Singleton.class 对象就起这种作用。

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

1.4、双重检验锁机制的同步锁单例模式

前面的实现方式目前已经能保证线程安全,但同时带来了延迟。

用来检查实例是否被创建的代码是同步的,那就意味着,此代码块在同一时刻只能被一个线程执行,但是同步锁(locking)只有在实例没有被创建的情况下才起作用。如果单例实例已经创建了,那么正常情况下,任何线程都应该可以用非同步方式获取当前的实例。

只有在单例对象未实例化的情况下,才进入到同步锁,那我们移动线程安全锁:

/**
     * 全局静态方法访问实例
     * @return
     */
    public static Singleton getInstance() {
        //1.先判断是否有实例
        if(instance == null) {
            synchronized(Singleton.class) {
                //2.同步代码块内再次检查是否有实例
                if(instance == null) 
                instance = new Singleton();
            }
        }
        return instance;
    }

这里我们看到判断了两次实例是否存在,因为我们需要保证在 synchronized 代码块中也要进行一次检查。
1.5、无锁的线程安全单例模式

Java 中单例模式的最佳实现形式中,类只会加载一次,通过在声明时直接实例化静态成员属性的方式来保证一个类只有一个实例。

这种实现方式避免了使用同步锁机制和判断实例是否被创建的额外检查。

package com.zhaoyanfei.designpattern.signleton;
/**
 * 无锁线程安全单例模式
 * @author zhaoYanFei
 *
 */
public class LockFreeSingleton {
 
    /**
     * 实例化静态成员属性
     */
    private static final LockFreeSingleton instance = new LockFreeSingleton();
 
    private LockFreeSingleton() {
        System.out.println("LockFreeSingleton is Instantiated.");
    }
    
    public static synchronized LockFreeSingleton getInstance() {
        return instance;
    }
    
    public void doSomething() {
        System.out.println("Something is done.");
    }
}

1.6、提前加载和延迟加载

提前加载和延迟加载的区别,就是实例对象被创建的时机。

如果在应用开始时创建单例实例,那就是提前加载单例模式。

如果在 getInstance 方法首次被调用时才调用单例构造器,那就是延迟加载单例模式。

前面的无锁线程安全单例模式在早期版本的 Java 中被认为是提前加载单例模式,但在最新版本的 Java 中,类只有在使用时才会被加载,所以它也是一种延迟加载模式。

另外,类加载的时机主要取决于 JVM 的实现机制,因而版本之间会有所不同。

目前,Java 语言并没有提供一种创建提前加载单例模式的可靠选项。如果确实需要提前实例化,可以在程序的开始通过调用 getInstance() 方法强制执行,如下面代码所示:

Singleton.getInstance();
1.7、我们所熟知的单例模式的应用
1.7.1、枚举类实现单例模式

《Effective Java》 推荐使用枚举的方式解决单例模式。这种方式解决了最主要的;线程安全、自由串行化、单一实例。
1.7.2、Jdk 源码中单例模式

java.lang.Runtime 使用了单例模式的饿汉式,源码如下:
0f7bf6afb37d4e3ca29a1fa2977bedc2.png
1.7.3、spring 源码中单例模式

在 Spring 依赖注入Bean实例默认是单例的,我们由此展开。bean 可以被定义为两种模式:prototype(原型|多例)和 singleton(单例)。

singleton(单例):只有一个共享的实例存在,所有对这个 bean 的请求都会返回唯一的实例。
prototype(多例):对这个 bean 的每次请求都会创建一个新的 bean 实例,类似于 new。

Spring 中加载单例的过程都是在 BeanFactory 的 getBean() 方法中被定义的,其默认的功能在 AbstractBeanFactory 类中实现,主要包含两个功能。

从缓存中获取单例 Bean。
Bean 的实例中获取对象。
getBean() 方法最终会调用 AbstractBeanFactory 的 doGetBean() 方法,源码如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    //对传入的beanName稍作修改,防止有一些非法字段,然后提取Bean的Name
    final String beanName = transformedBeanName(name);
    Object bean;
    //直接从缓存中获取单例工厂中的objectFactory单例
    Object sharedInstance = getsingleton(beanName);
    if (sharedInstance != null && args == null) {
        if (logger.isDebugEnabled()) {
            if (isSingletonCurrentlyInCreation(beanName)) {
                logger.debug("Returning eagerly cached instance of singleton bean '" +
                        beanName + "' that is not fully initialized yet - a consequence of a circular reference");
            } else {
            }
        }
        //返回对应的实例,从 Bean实例中获取对象
        bean = getObjectForBeanInstance(sharedInstance,name,beanName, null);
    } else {
        ...
    }
    ...
}

getBean() 方法不仅处理单例对象的逻辑,还处理原型对象的逻辑。继续看 getSingleton() 方法的代码实现。
getSingleton() 的工作流程:singletonObjects-->earlySingletonObjects-->singletonFactories-->创建单例实例

/**
* 单例对象的缓存
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //首先通过名字查找这个Bean是否存在
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //查看缓存中是否存在这个Bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            //如果这个时候的Bean实例还为空并且允许懒加载
            if (singletonObjects == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

上面代码片段中,synchronized(this.singletonObjects) 是关键,但是前提条件 isSingletonCurrentlyInCreation 的返回值也是 true,也就是这个 Bean 正在被创建。因此,第一次调用 doGetBean() 的时候,getSingleton() 基本上都是返回 null,所以会继续执行 doGetBean() 方法中后面的逻辑。

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    // 获取beanDefinition
    final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);
                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    for (String dep : dependsOn) {
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        registerDependentBean(dep, beanName);
                        try {
                            getBean(dep);
                        }
                        catch (NoSuchBeanDefinitionException ex) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                        }
                    }
                }
                // Create bean instance.
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // Explicitly remove instance from singleton cache: It might have been put there
                            // eagerly by the creation process, to allow for circular reference resolution.
                            // Also remove any beans that received a temporary reference to the bean.
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
            }
            ...
        }
}

可以看到,在 BeanFactory 中,从 XML 中解析出来的相关配置信息被放在 BeanDefinitionMap 中,通过这个 Map 获取 RootBeanDefinition,然后执行判断语句if(mbd.isSingleton())。如果是单例的,则接着调用 getSingleton() 的重载方法,传入 mbd 参数。当从缓存中加载单例对象时,会把当前的单例对象在singletonObjects 中存放一份,这样可以保证在调用 getBean() 方法的时候,singletonObjects 中永远只有一个实例,在获取对象时才会给它分配内存,既保证了内存高效利用,又是线程安全的。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
    // 直接从缓存中获取单例Bean
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            if (this.singletonsCurrentlyInDestruction) {
                throw new BeanCreationNotAllowedException(beanName,
                        "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                        "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
            }
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                // Has the singleton object implicitly appeared in the meantime ->
                // if yes, proceed with it since the exception indicates that state.
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    throw ex;
                }
            }
            catch (BeanCreationException ex) {
                if (recordSuppressedExceptions) {
                    for (Exception suppressedException : this.suppressedExceptions) {
                        ex.addRelatedCause(suppressedException);
                    }
                }
                throw ex;
            }
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 在singletonObject中添加要加载的单例
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

如此一来,当下次需要这个单例 Bean 时,可以直接从缓存中获取。在 Spring 中创建单例的过程虽然有点绕,但是逻辑非常清楚,就是将需要的对象放在 Map 中,下次需要的时候直接从 Map 中获取即可。

下一节为大家介绍工厂模式,感觉对您有所帮助的话,还望点赞、收藏+关注哦^_^。

相关文章
|
17天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
27 2
|
7天前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
10 1
|
23天前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入解析与实践
在PHP开发中,设计模式是提高代码可维护性、扩展性和复用性的关键技术之一。本文将通过探讨单例模式,一种最常用的设计模式,来揭示其在PHP中的应用及优势。单例模式确保一个类仅有一个实例,并提供一个全局访问点。通过实际案例,我们将展示如何在PHP项目中有效实现单例模式,以及如何利用这一模式优化资源配置和管理。无论是PHP初学者还是经验丰富的开发者,都能从本文中获得有价值的见解和技巧,进而提升自己的编程实践。
|
19天前
|
设计模式 安全 Java
C# 一分钟浅谈:设计模式之单例模式
【10月更文挑战第9天】单例模式是软件开发中最常用的设计模式之一,旨在确保一个类只有一个实例,并提供一个全局访问点。本文介绍了单例模式的基本概念、实现方式(包括饿汉式、懒汉式和使用 `Lazy&lt;T&gt;` 的方法)、常见问题(如多线程和序列化问题)及其解决方案,并通过代码示例详细说明了这些内容。希望本文能帮助你在实际开发中更好地应用单例模式,提高代码质量和可维护性。
26 1
|
12天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
20 0
|
15天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
本教程详细讲解了Kotlin中的单例模式实现,包括饿汉式、懒汉式、双重检查锁、静态内部类及枚举类等方法,适合需要深入了解Kotlin单例模式的开发者。快速学习者可参考“简洁”系列教程。
27 0
|
16天前
|
设计模式 存储 数据库连接
Python编程中的设计模式之美:单例模式的妙用与实现###
本文将深入浅出地探讨Python编程中的一种重要设计模式——单例模式。通过生动的比喻、清晰的逻辑和实用的代码示例,让读者轻松理解单例模式的核心概念、应用场景及如何在Python中高效实现。无论是初学者还是有经验的开发者,都能从中获得启发,提升对设计模式的理解和应用能力。 ###
|
18天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
18天前
|
设计模式 存储 安全
PHP中的设计模式:单例模式的深入解析与实践
在PHP开发中,设计模式是提高代码可维护性、扩展性和重用性的关键技术之一。本文将深入探讨单例模式(Singleton Pattern)的原理、实现方式及其在PHP中的应用,同时通过实例展示如何在具体的项目场景中有效利用单例模式来管理和组织对象,确保全局唯一性的实现和最佳实践。
|
22天前
|
设计模式 传感器 运维
Harmony设计模式-单例模式
Harmony设计模式-单例模式
45 0

热门文章

最新文章