ThreadLocal使用和源码分析

Posted study_zhxu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal使用和源码分析相关的知识,希望对你有一定的参考价值。

1.ThreadLocal是什么?

从名字可以看出来是线程局部变量的意思。ThreadLocal的功能非常简单,就是为每一个访问该变量的线程创建一个副本,使每一个线程可以独立的改变自己线程的副本,而不会和其他线程的副本有冲突。从线程的角度来看,就好像每个线程都有这个变量,当线程消失时,该副本也会被扔到GC中等待垃圾回收。

2.ThreadLocal API介绍

  • initialValue方法
protected T initialValue() 
    return null;

initialValue()方法是用来初始化变量值的。默认此方法返回的是null,如果需要null以外的初始值则需要复写此方法,返回需要的值。

一般来说在线程中此方法只会被调用一次,在第一次调用get()方法时调用了此方法,如果之前调用过set()方法,则不会再次调用该方法。但是如果之后调用了remove()方法,则之后再次使用时可能会再次调用该方法。

  • get()方法
public T get() 
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) 
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) 
            return (T) table[index + 1];
        
     else 
        values = initializeValues(currentThread);
    

    return (T) values.getAfterMiss(this);

get()方法返回该变量的值,通过代码可以看出首先获取当前线程对象,然后返回当前对象对应的values值,如果values为null,则电泳initializeValues(currentThread)方法创建该对象。

Values initializeValues(Thread current) 
    return current.localValues = new Values();

然后调用getAfterMiss(this)方法。

Object getAfterMiss(ThreadLocal<?> key) 
            Object[] table = this.table;
            int index = key.hash & mask;

            // If the first slot is empty, the search is over.
            if (table[index] == null) 
                //如果数组对应值为null,则调用初始化方法
                Object value = key.initialValue();

                // If the table is still the same and the slot is still empty...
                if (this.table == table && table[index] == null) 
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;

                    cleanUp();
                    return value;
                

                // The table changed during initialValue().
                put(key, value);
                return value;
            

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            // Continue search.
            for (index = next(index);; index = next(index)) 
                Object reference = table[index];
                if (reference == key.reference) 
                    return table[index + 1];
                

                // If no entry was found...
                if (reference == null) 
                    Object value = key.initialValue();

                    // If the table is still the same...
                    if (this.table == table) 
                        // If we passed a tombstone and that slot still
                        // contains a tombstone...
                        if (firstTombstone > -1
                                && table[firstTombstone] == TOMBSTONE) 
                            table[firstTombstone] = key.reference;
                            table[firstTombstone + 1] = value;
                            tombstones--;
                            size++;

                            // No need to clean up here. We aren't filling
                            // in a null slot.
                            return value;
                        

                        // If this slot is still empty...
                        if (table[index] == null) 
                            table[index] = key.reference;
                            table[index + 1] = value;
                            size++;

                            cleanUp();
                            return value;
                        
                    

                    // The table changed during initialValue().
                    put(key, value);
                    return value;
                

                if (firstTombstone == -1 && reference == TOMBSTONE) 
                    // Keep track of this tombstone so we can overwrite it.
                    firstTombstone = index;
                
            
        

可以看到在该方法中做了判断然后调用初始化方法返回初始值。

如果values不为null,则直接返回其中的值。

  • set()方法
public void set(T value) 
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) 
        values = initializeValues(currentThread);
    
    values.put(this, value);

该方法最后调用了put()方法,put()方法源码如下

void put(ThreadLocal<?> key, Object value) 
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) 
                Object k = table[index];

                if (k == key.reference) 
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                

                if (k == null) 
                    if (firstTombstone == -1) 
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) 
                    firstTombstone = index;
                
            
        

put()方法实现了存储的过程,其实就是将变量值存入到Object数组中。

  • remove()方法
public void remove() 
  Thread currentThread = Thread.currentThread();
  Values values = values(currentThread);
  if (values != null) 
      values.remove(this);
  


void remove(ThreadLocal<?> key) 
     cleanUp();

     for (int index = key.hash & mask;; index = next(index)) 
         Object reference = table[index];

         if (reference == key.reference) 
             // Success!
             table[index] = TOMBSTONE;
             table[index + 1] = null;
             tombstones++;
             size--;
             return;
         

         if (reference == null) 
             // No entry found.
             return;
         
     
 

移除此线程当前线程变量的值。

3.ThreadLocal与线程同步的区别

  • 同步锁采用以时间换空间的方式来操作变量,ThreadLocal采用以空间换时间的的方式操作变量
  • Java中synchronized是JVM的保留字,通过JVM的锁机制确保临界区的函数或变量的原子性。在同步机制中通过锁机制确保变量或方法在同一时间只能被一个线程操作,此时锁对象是被多个线程共享的。而ThreadLocal会为每一个线程创建一个和该线程绑定的副本,线程只需要操作该副本就可以了。因为线程操作的是本线程的副本对象,所以没有必要进行加锁了。
  • 本质上来说,同步锁机制是为了同步多个线程对一个对象操作的并发问题,一个资源被多个线程共享,多个线程通过该变量进行通信。而ThreadLocal从本质上讲是隔离线程。所以如果线程之间需要进行通信,则使用同步锁机制,如果需要隔离多线程之间的对共享资源的冲突则采用ThreadLocal。

4.ThreadLocal在android中的使用

在Android中典型的使用就是Handler使用ThreadLocal,在Android源码中,在创建Looper时,我们知道Looper是需要和当前线程进行绑定的

private static void prepare(boolean quitAllowed) 
    if (sThreadLocal.get() != null) 
        throw new RuntimeException("Only one Looper may be created per thread");
    
    sThreadLocal.set(new Looper(quitAllowed));

可以看到通过ThreadLocal的set()方法设置了Looper对象,通过之前的分析我们知道set()方法是实现绑定的线程和对象的,这样就完成了Looper与当前线程的绑定。

QQ交流群

微信公众号:Android在路上,欢迎关注

以上是关于ThreadLocal使用和源码分析的主要内容,如果未能解决你的问题,请参考以下文章

史上最详细之ThreadLocal源码分析

ThreadLocal源码分析

ThreadLocal 线程本地变量 及 源码分析

Java -- 基于JDK1.8的ThreadLocal源码分析

ThreadLocal 源码分析

ThreadLocal使用和源码分析