一种在线程池中透传或继承ThreadLocal信息的方法

在实际的业务代码中,经常会使用到 ThreadLocal 用于跨业务代码 来获取在上游设置的值。比如,在spring mvc中 spring web mvc 中通过 RequestContextHolder 设置 HttpServletRequest,业务代码则可以在 controller 或者是 service中 通过 RequestContextHolder#getRequestAttributes 获取相应的对象. 但这种方法有一个限制,即 setValue 和 getValue 的代码必须在同一个线程内. 当然,这也是属于通过 ThreadLocal 来避免竞争的一种手法.

针对之前已经可以工作的代码,如果将相应的业务代码 迁移至一个新的线程池中运行,即封装为 1个 runnable 对象,那么相应的代码即不能正确地工作了。

如下参考所示

ThreadLocal<String> local = new ThreadLocal<>();
local.set("value1");

//打印 获取->value1
System.out.println("获取->" + local.get());

Runnable runnable = () -> {
    System.out.println("获取->" + local.get());
};
//打印 获取->null
new Thread(runnable).start();

上面的 sout 可以是任意1个业务上的 method 调用。仅仅是将 method调用 转由新线程来运行,相应的业务逻辑即不能正常工作。 至于这里将调用转由新线程来运行,可以有很多的场景。如 支持 timedout 调用(利用future.get(timed))。

本文通过反射调用读取当前Thread的信息,将值注入到新线程中的threadLocal中,以达到透传threadLocal的目的。不需要修改任何业务代码,也不需要使用InheritableThreadLocal(此类也并不用于当前场景)

主要的实现基于以下步骤

  • 提前提取当前线程ThreadLocal变量值
  • 执行时复制至新线程中
  • 执行结束之后删除未变化值

本文中的代码均基于反射调用,需要打开相应的 Accessible 属性.

继续阅读“一种在线程池中透传或继承ThreadLocal信息的方法”

使用greys找到泄漏的本地线程变量值

在程序代码中,出现过这样一种情况,在一个新的线程组中,尝试去获取一个threadLocal的值,按照相应的程序代码,应该是不能获取到的。但是在实际的运行过程中,却发现能够获取到相应的变量值。
在我们的程序代码中,使用了shiro来存储相应的用户信息,即一个简化版的的权限管理程序。其中,在标准的web程序中,shiro拦截器会把相应的会话信息存储在session中,并且在应用代码中,可以通过一个threadLocal的ThreadContext来获取相应的subject,进而拿到相应的会话值.相应的代码如下所示:

    public static UserId getUserId() {
        Optional<Subject> optional = Optional.ofNullable(ThreadContext.getSubject());
        return optional.map(t -> t.getSession(false))
                .map(t -> t.getAttribute("userId"))
                .orElse(null);
    }

因为相应的数据为web调用时才会注入到sessionId,而如果是一个定时类的任务,那么理论上应该是不会有相应的session对象存在,那么即不会有相应的subject存在,在调用此方法时即会返回在调用getSubject时失败。但是在实际上时,却发生调用subject不为null,进而在调用getSession方法时出现了错误信息。

相应的调用链看起来应该像是这样

  1. 线程1调用了 ThreadContext.setSubject 方法,设置了session信息
  2. 线程1完成整个业务方法的运行,相应的session信息被销毁,但相应的removeSubject方法并没有被调用
  3. 线程1重新以任务的形式执行任务代码,其中调用了getSubject,但不能拿到相应的数据

在这种调用链中,我们只看到了最终的现场,但原始的设置值的现场即不能复现,即不清楚数据是什么时候,哪个调用任务进行处理的。整个问题称之为数据泄漏,即数据在不应该出现的地方出现了。
处理这种问题,一种作法即是异常标记法。详细的步骤如下所示:

  1. 在调用 ThreadContext.setSubject 时,设置一个异常线程栈(Exception),此里面封装了当前调用的整个路径
  2. 将异常线程栈(字符串形式)存储在一个threadLocal变量中,方便后面获取
  3. 在调用 getSubject 时,如果发生了相应的错误信息,即表示复现了相应的错误场景
  4. 这时候即将之前存储到threadLocal中的字符串获取出来,打印出原始的setSubject调用路径

整个作法参考于alibaba druid的数据库连接泄漏检测。

本文介绍了在不修改源代码的情况下,并且在线上环境,使用greys,来监控相应的调用,当出现了满足条件的场景时,自动打印出相应的调用路径。

继续阅读“使用greys找到泄漏的本地线程变量值”

安全有效地提升simpleDateFormat性能

    以下为转http://www.thedwick.com/blog/2008/04/simpledateformat-performance-pig/的一篇关于simpleDateformat中使用的一些误区以及怎么样来安全有效地使用simpleDateFormat。在本文中,描述了创造simpleDateFormat的昂贵代价,以及使用静态实例的多线程错误以及同步时的低效问题,并提出一个提升的解决办法。
    全文如下:

    Just yesterday I came across this problem “in the wild” for the third time in my career so far: an application with performance problems creating tons of java.text.SimpleDateFormat instances. So, I have to get this out there: creating a new instance of SimpleDateFormat is incredibly expensive and should be minimized. In the case that prompted this post, I was using JProfiler to profile this code that parses a CSV file and discovered that 50% of the time it took to suck in the file and make 55,000 objects out of it was spent solely in the constructor of SimpleDateFormat. It created and then threw away a new one every time it had to parse a date. Whew!

  “Great,” you think, “I’ll just create one, static instance, slap it in a field in a DateUtils helper class and life will be good.”
    Well, more precisely, life will be good about 97% of the time. A few days after you roll that code into production you’ll discover the second cool fact that’s good to know: SimpleDateFormat is not thread safe. Your code will work just fine most of the time and all of your regression tests will probably pass, but once your system gets under a production load you’ll see the occasional exception.
    “Fine,” you think, “I’ll just slap a ‘synchronized’ around my use of that one, static instance.”
    Ok, fine, you could do that and you’d be more or less ok, but the problem is that you’ve now taken a very common operation (date formatting and parsing) and crammed all of your otherwise-lovely, super-parallel application through a single pipe to get it done.
    What would be better is to use a ThreadLocal variable so you can have your cake and eat it, too:

继续阅读“安全有效地提升simpleDateFormat性能”