一个人失眠,全世界失眠
如何统计时间
一天,一尘看着钟表发呆。
慧能:一尘呀,在想什么呢
一尘:在想时间都去哪了
慧能:要想知道时间都去哪了,我们往往要记录下我们在什么时间段干了什么事。
一尘:嘿嘿,从来没有记过
慧能:哈哈,你的时间你自己要管理好,我们程序中有时也会记录下某一个函数或者某一段程序所花费的时间
说着说着,慧能写了一段程序
doSomething()是真正做事的函数,里面封装了一个具体的任务处理过程,你不可以改动,现在我想通过before()和after()来计算出doSomething()花费了多长时间
一尘:这还不简单,说着说着一尘就随手写了一段程序
经验丰富的慧能一眼就看出来这个程序的致命缺陷
慧能:这个程序在并发的情况下还能正常工作吗?
一尘:弟子方才鲁莽了,这个程序在并发的情况下有可能出错
线程1先设置了startTime,然后另一个线程2又设置了startTime,把线程1设置的startTime给覆盖了,当线程1运行after()的时候,拿到的是线程2设置的startTime,这显然是不正确的。
慧能:那应该怎么办呢?
一尘:我可以给service()方法上加一个锁。给service方法上加一个锁就意味着线程1在获得锁后执行service方法的时候,其他线程(比如线程2)就不可以执行service方法,那么也就不会在线程1没有执行完service()的时候去修改startTime的值。
随后一尘写了给service加锁的代码
这样并发的时候就不会出问题了
慧能:这个是一种解决方案,但是这样一来你的程序就串行化了,不能很好地利用多线程的优势,还有没有其他解决方案?
一尘:弟子不才,还望师傅指教
ThreadLocal
你想一下,如果线程1 在执行 before()的时候,把当前时间放到自己内部的某个地方,变成私有的,然后继续执行,等到执行 after() 的时候再把之前存储的时间拿出来,这样不就解决了并发的问题了。
因为这样一来,每个线程在执行before()的时候,把当前时间存到自己内部的某个地方,别的线程根本访问不了,也修改不了。
就像下图这样:
把当前时间存放到 value 里面去。用的时候拿出来,每个线程都有自己的value,不同线程互不干预。
一尘:哦,这个思路好,那如果我要存储两个不同的Value值怎么办?
慧能:其实你只需用一个数据结构Map来存储就行了。
一尘:哦,这个具体怎么做?
慧能:Java中的Thread类里有一个 threadLocals 变量,这个变量是一个ThreadLocalMap 类型,你可以把这个类型简单的理解为 Map 类型。
你可以把你想要存的值放入这个Map之中。
一尘:那这个key值是什么呢?
慧能:问的好,Java提供了一个类叫ThreadLocal,它的实例作为Key值
到时候使用的时候,将ThreadLocal的实例作为Key,你要存的值作为Value,把他们一块放入你的运行的线程(Thread)之中。
写成代码就是这个样子:
在before()里将当前的时间存放到执行before()的那个线程之中
startTimeThreadLocal作为Key,当前时间作为Value
等到你在after()方法里想用之前存的Value时,直接用startTimeThreadLocal.get()拿出来。
如果你想存储另一个Value值,很简单,再弄一个ThreadLocal
源码分析
一尘:好奇怪,这个怎么就放到了Thread里面去了。
慧能:这个看一下源码就知道了
如果 map 为 null 那么就创建一个map放入线程之中。
再来看一下 ThreadLocal中的 get() 方法源码
一尘:原来是这样做的呀,那什么时候我应该使用ThreadLocal呢
慧能:这个问题问的好,每种技术都有它的使用场景,当你需要将某个值与线程相关联,并且线程后面还会用到该值,你就可以使用ThreadLocal,比如上面例子中的 当前时间。
参考:
《Java并发编程实战》
《码农翻身》