JAVA多线程并发下的单例模式应用

    单例模式应该是设计模式中比较简单的一个,也是非常常见的,但是在多线程并发的环境下使用却是不那么简单了,今天给大家分享一个我在开发过程中遇到的单例模式的应用。

    首先我们先来看一下单例模式的定义:

    一个类有且仅有一个实例,并且自行实例化向整个系统提供。
    单例模式的要素:
    1.私有的静态的实例对象
    2.私有的构造函数(保证在该类外部,无法通过new的方式来创建对象实例)
    3.公有的、静态的、访问该实例对象的方法

    单例模式分为懒汉形和饿汉式

    懒汉式:

    应用刚启动的时候,并不创建实例,当外部调用该类的实例或者该类实例方法的时候,才创建该类的实例。(时间换空间)

    优点:实例在被使用的时候才被创建,可以节省系统资源,体现了延迟加载的思想。

    缺点:由于系统刚启动时且未被外部调用时,实例没有创建;如果一时间有多个线程同时调用LazySingleton.getLazyInstance()方法很有可能会产生多个实例。

    例子:

    
    publicclassSingletonClass{
     //私有构造函数,保证类不能通过new创建
    privateSingletonClass(){}
    privatestaticSingletonClassinstance=null;
     publicstaticSingletonClassgetInstance(){
    if(instance==null){
     //创建本类对象
     instance=newSingletonClass();
    }
    returninstance;
    } 
    }
    

    饿汉式

    应用刚启动的时候,不管外部有没有调用该类的实例方法,该类的实例就已经创建好了。(空间换时间。)

    优点:写法简单,在多线程下也能保证单例实例的唯一性,不用同步,运行效率高。

    缺点:在外部没有使用到该类的时候,该类的实例就创建了,若该类实例的创建比较消耗系统资源,并且外部一直没有调用该实例,那么这部分的系统资源的消耗是没有意义的。

    例子:

    
    publicclassSingleton{
    //首先自己在内部定义自己的一个实例,只供内部调用
    privatestaticfinalSingletoninstance=newSingleton();
    //私有构造函数
     privateSingleton(){
    }
    //提供了静态方法,外部可以直接调用
    publicstaticSingletongetInstance(){
    returninstance;
    }
    }
    下面模拟单例模式在多线程下会出现的问题
    /**
    *懒汉式单例类
    */
    publicclassLazySingleton{
    //为了易于模拟多线程下,懒汉式出现的问题,我们在创建实例的构造函数里面使当前线程暂停了50毫秒
    privateLazySingleton(){
    try{
    Thread.sleep(50);
    }catch(InterruptedExceptione){
    e.printStackTrace();
    }
    System.out.println("生成LazySingleton实例一次!");
    }
    privatestaticLazySingletonlazyInstance=null;
    publicstaticLazySingletongetLazyInstance(){
    if(lazyInstance==null){
    lazyInstance=newLazySingleton();
    }
    returnlazyInstance;
    }
    }
    

    测试代码:我们在测试代码里面新建了10个线程,让这10个线程同时调用LazySingleton.getLazyInstance()方法

    
    publicclassSingletonTest{
    publicstaticvoidmain(String[]args){
     //创建十个线程调
    for(inti=0;i<10;i++){
    newThread(){
    @Override
    publicvoidrun(){
    LazySingleton.getLazyInstance();
    }
    }.start();
    }
    }
    }
    

    结果:

    生成LazySingleton实例一次!
    生成LazySingleton实例一次!
    生成LazySingleton实例一次!
    生成LazySingleton实例一次!
    生成LazySingleton实例一次!
    生成LazySingleton实例一次!
    生成LazySingleton实例一次!
    生成LazySingleton实例一次!
    生成LazySingleton实例一次!
    生成LazySingleton实例一次!

    可以看出单例模式懒汉式在多线程的并发下也会出现问题,

    分析一下:多个线程同时访问上面的懒汉式单例,现在有两个线程A和B同时访问LazySingleton.getLazyInstance()方法。
    假设A先得到CPU的时间切片,A执行到if(lazyInstance==null)时,由于lazyInstance之前并没有实例化,所以lazyInstance==null为true,在还没有执行实例创建的时候

    此时CPU将执行时间分给了线程B,线程B执行到if(lazyInstance==null)时,由于lazyInstance之前并没有实例化,所以lazyInstance==null为true,线程B继续往下执行实例的创建过程,线程B创建完实例之后,返回。

    此时CPU将时间切片分给线程A,线程A接着开始执行实例的创建,实例创建完之后便返回。由此看线程A和线程B分别创建了一个实例(存在2个实例了),这就导致了单例的失效。

    解决办法:我们可以在getLazyInstance方法上加上synchronized使其同步,但是这样一来,会降低整个访问的速度,而且每次都要判断。

    那么有没有更好的方式来实现呢?我们可以考虑使用”双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受到很大的影响。我们看看具体解决代码

    
    publicclassLazySingleton{
    privateLazySingleton(){
    try{
    Thread.sleep(50);
    }catch(InterruptedExceptione){
    e.printStackTrace();
    }
    System.out.println("生成LazySingleton实例一次!");
    }
    privatestaticLazySingletonlazyInstance=null;
    publicstaticLazySingletongetLazyInstance(){
     //先检查实例是否存在,如果不存在才进入下面的同步块
    if(lazyInstance==null){
     //同步块,线程安全地创建实例
     synchronized(LazySingleton.class){
     //再次检查实例是否存在,如果不存在才真正地创建实例
     if(lazyInstance==null){
      lazyInstance=newLazySingleton();
      }
     }
    }
    returnlazyInstance;
    }
    }
    

    这样我们就可以在多线程并发下安全应用单例模式中的懒汉模式。这种方法在代码上可能就不怎么美观,我们可以优雅的使用一个内部类来维护单例类的实例,下面看看代码

    
    publicclassGracefulSingleton{
    privateGracefulSingleton(){
    System.out.println("创建GracefulSingleton实例一次!");
    }
    //类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
    privatestaticclassSingletonHoder{
        //静态初始化器,由JVM来保证线程安全
    privatestaticGracefulSingletoninstance=newGracefulSingleton();
    }
    publicstaticGracefulSingletongetInstance(){
    returnSingletonHoder.instance;
    }
    }

    说一下我在实际开发中的场景:为了程序的高效率使用多线程并发,然而是循环调用,可能导致创建线程数过多,考虑采用线程池管理,这时候创建线程池仍然是处于循环调用中,也可能导致多个线程池,这时候就考虑使用单例模式。

    源代码:

    
    publicclassThreadPoolFactoryUtil{
    privateExecutorServiceexecutorService;
     //在构造函数中创建线程池
    privateThreadPoolFactoryUtil(){
    //获取系统处理器个数,作为线程池数量
    intnThreads=Runtime.getRuntime().availableProcessors();
    executorService=Executors.newFixedThreadPool(nThreads);
    }
    //定义一个静态内部类,内部定义静态成员创建外部类实例
    privatestaticclassSingletonContainer{
    privatestaticThreadPoolFactoryUtilutil=newThreadPoolFactoryUtil();
    }
    //获取本类对象
    publicstaticThreadPoolFactoryUtilgetUtil(){
    returnSingletonContainer.util;
    }
    publicExecutorServicegetExecutorService(){
    returnexecutorService;
    }
    }

    涉及到一个静态内部类,我们看看静态内部类的特点:

    1、静态内部类无需依赖于外部类,它可以独立于外部对象而存在。
    2、静态内部类,多个外部类的对象可以共享同一个内部类的对象。
    3、使用静态内部类的好处是加强了代码的封装性以及提高了代码的可读性。
    4、普通内部类不能声明static的方法和变量,注意这里说的是变量,常量(也就是finalstatic修饰的属性)还是可以的,而静态内部类形似外部类,没有任何限制。可以直接被用外部类名+内部类名获得。

    以上是我在实际开发中遇到的一些问题,部分摘自网上代码,结合实开发际案例。如有不妥,希望大家及时指出!