buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

单例模式(懒汉式单例 and 饿汉式单例)

1/4 单例模式(单件模式)Singleton Pattern

单例模式,其在整个应用程序的生命周期中只存在一个实例。

本文介绍两种单例模式,以及,多线程并发情况下的懒汉式单例模式改造及代码分析。

 

2/4 懒汉式单例---需要的时候(首次被调用的时候)才创建实例

public class Singleton
{
    //定义一个私有的静态全局变量来保存该类的唯一实例
    private static Singleton singleton;

    /// <summary>
    /// 构造函数必须是私有的
    /// 这样在外部便无法使用 new 来创建该类的实例
    /// </summary>
    private Singleton()
    {
    }

    /// <summary>
    /// 定义一个全局访问点
    /// 设置为静态方法
    /// 则在类的外部便无需实例化就可以调用该方法
    /// </summary>
    /// <returns></returns>
    public static Singleton GetInstance()
    {
        if (singleton == null)
        {
            singleton = new Singleton();
        }
        return singleton;
    }
}

下面是改进后的懒汉式单例类,使其在多线程的环境下也可以实现单例模式的功能。方案是使用双检锁(双重检查锁定,Double-Check Locking)

lock锁是保证线程同步执行的。当多个线程同时到达,一个线程持有lock锁之后,其他线程处于阻塞状态,直到第一个线程释放lock锁。

第一重 singleton == null 的意义:
这里就涉及一个性能问题了,因为对于单例模式的话,new Singleton()只需要执行一次就 OK 了,
而如果没有第一重 singleton == null 的话,每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的。
为了解决线程同步带来的性能问题,我们加上第一重 singleton == null。
那么,只有在最初singleton ==null 的情况下,才会加锁实现线程同步。注意这里,并发情况下,只要执行到singleton ==null ,那么,当一个线程上锁直到对象初始化释放后,其他blocked的线程也会上锁。也就是说,会存在多个线程持有锁,这时,第二重singleton == null的判断就奏效了。后面的线程虽然持有了锁,但是因为singleton已经不为null,所以不会重复创建。

而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了。

@Slf4j
public class SigngletonClass {
    private static volatile SigngletonClass singleton;
    // 定义一个只读静态对象,且这个对象是在程序运行时创建的
    private static final Object object = new Object();

    private SigngletonClass() {
    }

    public static SigngletonClass getSingleton() {
        // 第一重 singleton == null
        if (null == singleton) {
            log.info("线程进入第一重 singleton == null");
            synchronized (object) {
                log.info("线程进入同步区");
                // 第二重 singleton == null
                if (null == singleton) {
                    log.info("创建实例start");
                    try {
                        Thread.sleep(new Random().nextInt(50));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    singleton = new SigngletonClass();
                    log.info("创建实例end");
                }
            }
        }
        return singleton;
    }
}

 

模拟多线程并发访问testcase。用例中有5个线程并发。从后面的执行结果可以看出,一共10个线程,前5个线程都加锁进入了同步区。后面的5个线程在获取单例时,由于singleton 已经不为null,所以程序直接返回了singleton 实例。

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SigngletonClassTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);

        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> {
                SigngletonClass.getSingleton();
                log.info("线程结束");
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(5, TimeUnit.SECONDS);
    }
}

 

如下是testcase的执行结果:

18:52:59.778 [pool-1-thread-5] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null
18:52:59.778 [pool-1-thread-3] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null
18:52:59.778 [pool-1-thread-1] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null
18:52:59.793 [pool-1-thread-5] INFO singletonpattern.SigngletonClass - 线程进入同步区
18:52:59.779 [pool-1-thread-2] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null
18:52:59.793 [pool-1-thread-5] INFO singletonpattern.SigngletonClass - 创建实例start
18:52:59.778 [pool-1-thread-4] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null
18:52:59.805 [pool-1-thread-5] INFO singletonpattern.SigngletonClass - 创建实例end
18:52:59.805 [pool-1-thread-5] INFO singletonpattern.SigngletonClassTest - 线程结束
18:52:59.805 [pool-1-thread-4] INFO singletonpattern.SigngletonClass - 线程进入同步区
18:52:59.805 [pool-1-thread-4] INFO singletonpattern.SigngletonClassTest - 线程结束
18:52:59.805 [pool-1-thread-5] INFO singletonpattern.SigngletonClassTest - 线程结束
18:52:59.806 [pool-1-thread-4] INFO singletonpattern.SigngletonClassTest - 线程结束
18:52:59.806 [pool-1-thread-5] INFO singletonpattern.SigngletonClassTest - 线程结束
18:52:59.806 [pool-1-thread-4] INFO singletonpattern.SigngletonClassTest - 线程结束
18:52:59.806 [pool-1-thread-5] INFO singletonpattern.SigngletonClassTest - 线程结束
18:52:59.806 [pool-1-thread-2] INFO singletonpattern.SigngletonClass - 线程进入同步区
18:52:59.806 [pool-1-thread-2] INFO singletonpattern.SigngletonClassTest - 线程结束
18:52:59.806 [pool-1-thread-1] INFO singletonpattern.SigngletonClass - 线程进入同步区
18:52:59.806 [pool-1-thread-1] INFO singletonpattern.SigngletonClassTest - 线程结束
18:52:59.807 [pool-1-thread-3] INFO singletonpattern.SigngletonClass - 线程进入同步区
18:52:59.807 [pool-1-thread-3] INFO singletonpattern.SigngletonClassTest - 线程结束

 

 

3/4 饿汉式单例--程序加载的时候就创建实例(jvm类的加载过程)

在c#中,可以使用静态初始化来完成饿汉式单例

public sealed class Singleton
{
    private static readonly Singleton singleton = new Singleton();

    private Singleton()
    {
    }

    public static Singleton GetInstance()
    {
        return singleton;
    }
}

 

 

4/4 结束

  1.  本文参考 https://www.cnblogs.com/BoyXiao/archive/2010/05/07/1729376.html ,博主在懒汉式改造中,对双检锁的解释比较清晰。不过,博主说的加了双检锁之后,只会存在一个线程进入同步区。我对此存不同观点,抱着认真学习的态度,特意编写上文的testcase并做测试。
  2. 返回单例的那个方法名,本文是叫getInstance(),有的系统取名是me(),感觉既精简又更易读。

 

posted on 2020-09-25 19:08  buguge  阅读(98)  评论(0编辑  收藏  举报