码迷,mamicode.com
首页 > 编程语言 > 详细

线程的私家小院儿:ThreadLocal

时间:2016-07-17 13:22:21      阅读:213      评论:0      收藏:0      [点我收藏+]

标签:

转载自simplemain老王的公众号

  话说在《操作系统原理》这门课里面,我们学到了很多概念:进程、线程、锁、PV操作、读写者问题等等,其中关于进程、线程和锁的东西是我们平时工作中用到最多的:服务器接收到用户请求,需要用一个进程或者一个线程去处理,然后操作内存、文件或者数据库的时候,可能需要对他们进行加锁操作。

不过作为一个有追求的程序员,我们有些时候会不满足于此 。于是,我们开始对线程、锁开始了漫漫的优化之路。其中有一种情况是非常值得优化的:假定我们现在有一个web服务,我们的程序一般都会为每一个到来的请求创建一个上下文环境(比如:请求的URL、浏览器型号、访问的token等等),然后这个上下文贯穿了这一次请求,任何处理操作都可以拿到这个上下文。那我们实现的时候,就可以这样来做:

技术分享

我们设计一个ContextManager,用来管理所有的context。他可能是一个单例(singleton)模式,也可能提供静态(static)方法,反正不管如何,全局都可以访问,我们这里为了说明简单,就假定是用static方法。然后请求的处理线程在接收到任务的时候,就调用这个ContextManager的getContext()方法,提供给这个线程一个context,然后线程对这个context做初始化等等操作,供后续使用。

我们来考虑一下ContextManager.getContext()的实现:为了保证多个线程访问时候的安全,我们在绝大多数情况下,会对这个函数加锁(或者部分代码块加锁),不过,如果访问量很大的话,这个加锁就会导致性能下降(特别是线程很多的时候,这个情况就越发的明显),很多线程会在锁上进行等待(linux下可以用strace,java可以用jstack等工具来查看),其造成的结果就是处理速度变慢,并发能力下降。那有没有好的解决方案呢?

第一次改进:全局锁 -> 线程内部数据

大家想,对于context而言,只要有用户请求进来,他的这一段青春就已经献给了这次请求,换句话说,实际上就是把自己献给了这个线程。那既然这样,我们是不是就可以把context的归属权让给thread(将context变成thread的属性),而不是ContextManager呢?这样,context不光是一段青春给了thread,更是把一生都献给了thread~

 技术分享

伪代码大体上就写成了这样:

class Thread{
    //成为线程的一个成员变量       
    private Context context;
    public Context getContext() {  
        return context;
    }  
}

如果一个请求过来了,我们的工作线程直接就初始化自己的context环境,然后供后续逻辑处理使用,再也不用去求别人加个锁分配个context了。是不是一个很棒的方案呢?

只要少去了锁,效率确实就极大的提升,看起来我们就不用往下讲了,因为之前说的问题都解决了,是吗?

其实不完全,比方说,除了context,我还要放点其他的东西,如:日志文件的fd、数据库的连接…… 这怎么办呢?一种办法是,我们在Thread这个类里面,再继续填充各种东西(定义属性),比如:

class Thread{

    private Context context;

    private int logFd;

    private MysqlConnection conn;

}

那如果再加东西呢?按照这种方式,不但写起来麻烦,而且扩展能力相当差。我们怎么改进呢?

第二次改进:线程内成员归一到Map

其实也很简单,我们不是有一种神奇的数据结构叫做map(有红黑树、Hash等实现版本)么?他就能帮我们来管理这些烂七八糟的东东啊。

技术分享 

于是乎,我们原来的那些代码,就可以这样的修改啦:

class Thread{

    private Map<String, Object> threadMap;

    public Map<String, Object> getThreadMap(){

        return threadMap;

    }

}

线程创建并初始化的时候,执行以下代码:

Thread thread = Thread.getCurrentThread();

Map<String,Object> map = thread.getThreadMap();

map.put("context", new Context());

map.put("logFd", (new Log()).getFd());

map.put("mysqlConnection", ConnectionPool.getConnection());

这样,我们的代码就有非常好的一个扩展性了,想放啥放啥,对吧。而且请求来了以后,也不加锁,程序跑的飞快!如果你的程序要放些啥进去,也没啥问题。不过,就是唯一有点问题,你的程序要记住各种字符串组成的key,比如:"context"、"logFd"等等。虽然也不是什么问题吧,不过也有些不是太完美。而且如果代码是多个人写的话,还有可能出现key的命名冲突,对吧(谁把谁覆盖了都不知道,然后各种debug,最后发现了问题,只能说一句:我擦!)。

那我们又该怎么样来解决这个问题呢?

第三次改进:对象地址取代命名

其实也不难,既然字符串容易造成混乱,我们把字符串换掉,换成一个不重复的东西不就结了嘛?那什么东西不会重复呢?很简单,内存地址啊!于是,我们就可以把代码改成这样:

class ThreadMapKey{
}
class Thread{

    private Map<ThreadMapKey, Object> threadMap;

    public Map<ThreadMapKey, Object> getThreadMap(){        

        return threadMap;

    }

}

全局建立几个对象:

static ThreadMapKey context = new ThreadMapKey();

static ThreadMapKey logFd = new ThreadMapKey();

static ThreadMapKey mysqlConnection = new ThreadMapKey();

线程初始化的时候调用:

Thread thread = Thread.getCurrentThread();

Map<ThreadMapKey, Object> map = thread.getThreadMap();

map.put(context, new Context());

map.put(logFd, (new Log()).getFd());

map.put(mysqlConnection, ConnectionPool.getConnection());

我们定义一个叫做ThreadMapKey的类,这个类啥事儿不干,他就是一个摆设。当全局初始化的时候,我们新建几个他的实例,比如:context、logFd、mysqlConnection等,然后把他们当做ThreadMap的Key。这样,不同的开发者再也不用担心自己起的名字会冲突了,因为只要对象不一样,他们的内存地址就是不一样的,我们用他做的Key就是不一样的。

好了,这样看起来似乎已经很完美了。不过呢,对于追求极致美的程序员而言,他们不甘心,觉得还有瑕疵:每次要从这个线程取线程局部数据的时候,代码写起来都麻烦的很。具体看如下:

Thread thread = Thread.getCurrentThread();

Map<ThreadMapKey,Object> map = thread.getThreadMap();

Object obj = map.get(context);

Context value = (Context)obj;

这样的代码看起来似乎太不优美了,要写这么多行代码…… 我们如何优化呢?

第四次改进:包装

如果我们把上述代码包装起来,是不是就不用每次都写了呢?那怎么包装呢?我们的ThreadMapKey就是一个很好的东东,我们就让他提供一个函数,用来包装。说干就干,看看代码:

class ThreadMapKey <T>{
    public T getValue(){
            Thread thread = Thread.getCurrentThread();
            Map<ThreadMapKey, Object>map = thread.getThreadMap();
            Object obj = map.get(this);
            T value = (T)obj;
            return value;
    }
 
    public void setValue(T value){
            Thread thread =Thread.getCurrentThread();
            Map<ThreadMapKey, Object>map = thread.getThreadMap();
            map.put(this, value);
    }
}
 
class Thread{
    private Map<ThreadMapKey, Object> threadMap;
 
    public Map<ThreadMapKey, Object> getThreadMap(){   
            return threadMap;
    }  
}
 
static ThreadMapKey context = new ThreadMapKey();
static ThreadMapKey logFd = new ThreadMapKey();
static ThreadMapKey mysqlConnection = new ThreadMapKey();
 
// init
context.setValue(new Context());
logFd.setValue((new Log()).getFd());
mysqlConnection.setValue(ConnectionPool.getConnection());
 
// get per query
Context value = context.getValue();

我们将ThreadMapKey这个类增加了两个方法:getValue和setValue,他们分别从当前线程中取出ThreadMap,然后根据this关键字来获取和设置value。而且用到了泛型,这样取出来的值就可以直接赋值,而不用再自己写代码来做类型强转。这下再看代码,是不是简洁了很多很多。

其实这些都是java的一个叫做ThreadLocal类引出来的事儿--->线程这玩意儿可以私藏数据,然后还不用加锁

我们把

ThreadMapKey ->ThreadLocal,

ThreadMap -> ThreadLocalMap

做了这样的一个转换以后,再来看Java的代码:

 

public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {

    /**
     * Creates a thread local variable.
     */
    public ThreadLocal() {
    }

    /**
     * Returns the value in the current thread‘s copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread‘s value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }


    /**
     * Sets the current thread‘s copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread‘s copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
}

看看,是不是和我们的实现很像啊。我们的应用代码怎么写呢?

private static final ThreadLocal<Session> sessions = new ThreadLocal<>();
    
public static Session getThreadSession(){
    Session session = sessions.get();
    if (session == null){
       session = new Session();
       sessions.set(session);
    }
    return session;
}

ThreadLocalMap的实现,没有用标准的Map,而是自己实现了一个HashMap(有兴趣的同学可以去读读源代码,就是我们教科书上讲的经典hash实现)。这个Map里的Entry类,采用了弱引用的方式(而不是强引用),这样的好处,就是如果有对象不再使用的时候,就会被系统回收,而不是被这个Map继续持有引用,而造成系统不可回收,进而使得内存泄露(此处给Java代码的实现者鼓掌!)

总结一下:在了解了实现原理以后,如果你有跟线程相关的数据而又可以不加锁的,就尽管使用ThreadLocal吧,真的很好用。他可以让你的程序有更高的效率,更好的代码整洁度。

 

线程的私家小院儿:ThreadLocal

标签:

原文地址:http://www.cnblogs.com/winner-0715/p/5677668.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!