ThreadLocal深入理解2

                       ThreadLocal的相关知识

ThreadLocal知识

在线程编程中必须注意对临界资源的处理,当多个线程读取临界资源时,应当对资源加锁,防止访问冲突。在java编程中,多线程访问共享资源的时候,也需要加锁保证线程访问安全,一般使用synchronized关键字标示。但加锁有一个很大的缺点,影响执行速度,因为加锁的区域只能有单一线程访问。所以,一般对于需要加锁的区域,都尽量使得代码区域越小越好,通过提高锁的粒度来使得同步造成的性能影响尽可能小。

但其实如果能够将临界资源变成非临界资源,那么我们就不需要对资源加锁,当然同步所造成的性能影响就消失了。比如数据库连接Connection,如果我们可以保证每个线程都有一个属于自己的Connection,多线程访问Connection都只访问属于自己的连接,Connection就不在需要同步了。如果每个线程都有一个Connection,那么是否需要对每个线程的Connection定义不同的名称呢,显然这样的编程方式很不灵活(对多线程执行的代码需要传递额外的判断参数或进行额外的判断)。

Java中的ThreadLocal可以保证每个线程拥有一份数据副本,同时访问的时候又不需要额外判断数据属于哪个线程,每个线程都使用统一的名称访问属于自己的那份数据副本。

ThreadLocal中方法:

T get() // 返回此线程局部变量的当前线程副本中的值。

protected T initialValue() //返回此线程局部变量的当前线程的“初始值”。

Note : initialValue方法是protected的,也就是说我们可以覆盖该方法(一般采用匿名类的方式),提供给ThreadLocal不同的初始值,在ThreadLocal中,默认的initialValue返回的值是null(这个值可能不能满足我们对初始值的需求)。

void remove() // 移除此线程局部变量当前线程的值。

void set(T value) //将此线程局部变量的当前线程副本中的值设置为指定值。

ThreadLocal使用方法:

     比如对于Connection,可以定义:static ThreadLocal<Connection> connTreadLocal = new ThreadLoca<Connection>();

对于方法:getConnection(),这个方法可以被多个线程访问,使用了ThreadLocal,就可以不用对这个方法加锁了,具体方法可以如下:

public static Connection getConnection() {

              if(coonThreadLocal.get() == null) {

                     Connection conn = ConnectionManager.getConnection();

                     connTreadLocal.set(conn);

                     return conn;

              } else {

                     return connThreadLocal.get();

              }

  }

ThreadLocal的原理

    在ThreadLocal中有一个类:ThreadLocalMap,这个类就是用来存储一个线程中的所有ThreadLocal变量的,在Thread类中,可以看到这样的变量定义:ThreadLocal.ThreadLocalMapthreadLocals = null;正是由threadLocals来保存一个线程中所有的ThreadLocal变量。在ThreadLocalMap中,是采用一个Entry数组:

Entry[] table来保存线程的所有ThreadLocal变量的。Entry的构造函数是:private Entry(ThreadLocal k, Object v),也就是每个Entry都是把ThreadLocal对象本事做为一个key,ThreadLocal的值作为value。

 

在ThreadLocal中void T set()的实现代码是:

public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

 // getMap(t)实际是返回t中的 threadLocals(上面提到了Thread中有个

// ThreadLocalMap的变量threadLocals)

        if (map != null) // 如果map存在,直接往里面放数据

            map.set(this, value);

        else

            createMap(t, value); // 否则先创建,然后放入数据

}

 

在ThreadLocal中public T get()方法实现代码是:

public T get() {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

// 可以看出,查找值的时候正是以ThreadLocal对象作为key进行查找的(可以参考Entry的

// 构造函数),实际查找是到ThreadLocalMap中存放数据的Entry[] table中进行的

        if (map != null)

// 当map.get(this)找不到值时,会创建一个以this作为key的Entry,同时初始值为

// initialValue(),并返回这个initialValue值

            return (T)map.get(this);

        T value = initialValue();

        createMap(t, value);

        return value;

}

     由于每个线程的所有ThreadLocal数据均是放在ThreadLocalMap中的,故当调用public T set()的时候,实际上是会调用ThreadLocalMap的set(ThreadLocal key, Object value),ThreadLocalMap中的这个方法如下:

private void set(ThreadLocal key, Object value) {

            Entry[] tab = table;

            int len = tab.length;

            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];

                  e != null;

                  e = tab[i = nextIndex(i, len)]) {

            ThreadLocal k = e.get();

            if (k == key) {

                e.value = value;

                return;

                }

                if (k == null) {

                    replaceStaleEntry(key, value, i, false);

                    return;

                }

            }

            tab[i] = new Entry(key, value);

            int sz = ++size;

            if (!cleanSomeSlots(i, sz) && sz >= threshold)

                rehash();

        }

    可以看出在设值的时候,使用了hash的方式找一个位置,然后在这个位置i上设置,但既然是hash的方式,就有可能导致hash冲突,可以看出当冲突的时候,代码采用了简单的线性查找的方式找一个可用的位置,来存放数据的。采用线性的方式如何防止更多的hash冲突,可能与ThreadLocal设置的hashcode方式有关吧(还不是很明白其中的数学道理)。每个ThreadLocal都有一个hashcode,并且对于不同的ThreadLocal的hashcode,增长的方式是:   

HASH_INCREMENT = 0x61c88647; // 为什么是设置这个间隔值,应该有数学上的原理吧

nextHashCode = h + HASH_INCREMENT;

总结:

    使用ThreaLocal是一种利用空间将临界资源变成非临界资源的一种方式,同时它可以使得多线程以统一的方式访问自已线程的变量副本,减少了多线程编程中的参数传递。但是,它并不能解决多线程中共享资源访问的加锁问题。对于多个线程均需要访问的共享资源,比如只有一台打印机等,仍然需要考虑访问冲突。如果按照ThreadLocal的方式,那就应该给每个线程分派一台打印机。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值