暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

线程上下文设计模式(上)

Alleria Windrunner 2020-05-26
694

什么是上下文

关于上下文(context),我们在开发的过程中经常会遇到,比如开发 struts2 的 ActionContext、Spring 中的 ApplicationContext,上下文是贯穿整个系统或阶段生命周期的对象,其中包含了系统全局的一些信息,比如登录之后的用户信息、账号信息,以及在程序每一个阶段运行时的数据。
在之前文章中定义的单例对象实际上也是上下文,并且它是贯穿整个程序运行的生命周期的上下文,比如以下代码就是典型的使用单例对象充当系统级别上下文的例子:
    public final class ApplicationContext
    {
    //在 Context 中保存 configuration 实例
    private ApplicationConfiguration configuration;
    //在 Context 中保存 runtimeinfor 实例
    private RuntimeInfo runtimeInfo;
    //...其他


    //采用 Holder 的方式实现单例
    private static class Holder
    {
    private static ApplicationContext instance = new ApplicationContext();
    }


    public static ApplicationContext getContext()
    {
    return Holder.instance;
    }


    public void setConfiguration(ApplicationConfiguration configuration)
    {
    this.configuration = configuration;
    }


    public ApplicationConfiguration getConfiguration()
    {
    return this.configuration;
    }


    public void setRuntimeInfo(RuntimeInfo runtimeInfo)
    {
    this.runtimeInfo = runtimeInfo;
    }


    public RuntimeInfo getRuntimeInfo()
    {
    return this.runtimeInfo;
    }
    }
    在上面的代码中我们不难发现,如果 configuration 和 runtimeInfo 的生命周期会随着被创建一直到系统运行结束,我们就可以将 ApplicationContext 称为系统的上下文,诸如 configuration 和 runtimeInfo 等其他实例属性则称为系统上下文成员。
    当然在设计系统上下文时,除了要考虑到它的全局唯一性(单例设计模式保证)之外,还要考虑到有些成员只能被初始化一次,比如配置信息的加载,以及在多线程环境下,上下文成员的线程安全性。


    线程上下文设计

    在有些时候,单个线程执行的任务步骤会非常多,后一个步骤的输入有可能是前一个步骤的输出,比如在单个线程多步骤(阶段)执行时,为了使得功能单一,有时候我们会采用 GoF 职责链设计模式,如下图所示。

    虽然有些时候后一个步骤未必会需要前一个步骤的输出结果,但是都需要将 context 从头到尾进行传递,假如方法参数比较少还可以容忍,如果方法参数比较多,在七八次的调用甚至十几次的调用,都需要从头到尾地传递 context,很显然这是一种比较烦琐的设计,那么我们就可以尝试采用线程的上下文设计来解决这样的问题。
      private ConcurrentHashMap<Thread, ActionContext> contexts =
      new ConcurrentHashMap<>();
      public ActionContext getActionContext()
      {
      ActionContext actionContext = contexts.get(Thread.currentThread());
      if (actionContext == null)
      {
      actionContext = new ActionContext();
      contexts.put(Thread.currentThread(), actionContext);
      }
      return actionContext;
      }
      不同的线程访问 getActionContext()方法,每一个线程都将会获得不一样的 Action-Context 实例,原因是我们采用 Thread.currentThread()作为 contexts 的 key 值,这样就可以保证线程之间上下文的独立性,同时也不用考虑 ActionContext 的线程安全性(因为始终只有一个线程访问 ActionContext),因此线程上下文又被称为“线程级别的单例”。
      注意,通过这种方式定义线程上下文很可能会导致内存泄漏,contexts 是一个 Map 的数据结构,用当前线程做 key,当线程的生命周期结束后,contexts 中的 Thread 实例不会被释放,与之对应的 Value 也不会被释放,时间长了就会导致内存泄漏(Memory Leak),当然可以通过 soft reference 或者 weak reference 等引用类型,JVM 会主动尝试回收。
      文章转载自Alleria Windrunner,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论