ThreadLocal原理实现

之前有在项目中应用过ThreadLocal的示例,不过用的时候,也只是大致了解了ThreadLocal的应用场景,对于它的实现原理,并没有去深入看过。正巧在办公桌上的《java高并发程序设计》中看到了,遂了解一番,记录一波。
ThreadLocal是线程的局部变量,线程间无法读取彼此的数据,只能在当前线程访问到数据,是线程安全的。常见的应用场景:管理数据库的Connection。

书中的示例代码如下:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Description: 
* * @author: name:yuxin * Create Time: 2018/5/28 0028-下午 10:10
*/
public class TlTest { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static class ParseDate implements Runnable{ int i = 0; public ParseDate(int i){ this.i=i; } public void run(){ Date t = null; try { t = sdf.parse("2018-05-28 22:12:"+i%60); } catch (ParseException e) { e.printStackTrace(); } System.out.println(i+":"+t); } } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { es.execute(new ParseDate(i)); } } }

由于SimpleDateFormat不是线程安全的,所以在代码运行中报错如下:

Exception in thread "pool-1-thread-18" java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:601)
    at java.lang.Long.parseLong(Long.java:631)
    at java.text.DigitList.getLong(DigitList.java:195)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at TlTest$ParseDate.run(TlTest.java:26)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

使用ThreadLocal解决上述问题

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Description: 
* * @author: name:yuxin * Create Time: 2018/5/28 0028-下午 10:10
*/
public class TlTest { private static ThreadLocal threadLocal = new ThreadLocal<>(); public static class ParseDate implements Runnable{ int i = 0; public ParseDate(int i){ this.i=i; } public void run(){ Date t = null; if(threadLocal.get()==null){ threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); } SimpleDateFormat sdf = threadLocal.get(); try { t = sdf.parse("2018-05-28 22:12:"+i%60); } catch (ParseException e) { e.printStackTrace(); } System.out.println(i+":"+t); } } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { es.execute(new ParseDate(i)); } } }

ThreadLocal的实现原理

在上述的应用中,可以了解到ThreadLocal有set(),get()两个方法

set的实现如下

public void set(T value) {
    //1.获取到当前线程对象
    Thread t = Thread.currentThread();
    //2.通过当前线程对象获取到ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    //3.若获取到的ThreadLocalMap不为空,则以当前ThreadLocal对象作为key,将值存储
    if (map != null)
        map.set(this, value);
    else
    //4.若获取到的ThreadLocalMap为空,则创建ThreadLocalMap对象
        createMap(t, value);
}

get的实现如下

public T get() {
    //1.获取到当前线程对象
    Thread t = Thread.currentThread();
    //2.通过当前线程对象获取到ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    //3.若获取到的ThreadLocalMap不为空,则以当前ThreadLocal对象作为key,将值取出,并返回值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //4.若获取到的ThreadLocalMap为空,则进行初始化,并将初始化的值返回
    return setInitialValue();
}

通过以上代码,我们可以了解到ThreadLocal的值是存在于Thread的内部类ThreadLocalMap中的,在ThreadLocalMap中,key为ThreadLocal当前对象的弱引用。
当线程执行完毕,退出时,为了ThreadLocalMap能尽快被清理,会执行如下操作


    /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        //加速ThreadLocalMap被清理
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

如上,是在线程结束时,会执行的操作。但是,如果在一个应用了线程池的操作中使用了ThreadLocal,会隐藏着内存泄漏的风险。因为当线程结束后,线程并没有被exit,而是放回了线程池中,以后若不再使用ThreadLocalMap中的值,由于线程一直存在,ThreadLocalMap是无法被回收的。因此在该场景中使用ThreadLocal时,应注意在使用后,及时调用ThreadLocal.remove()方法,该方法代码如下:

/**
 * Removes the current thread's value for this thread-local
 * variable.  If this thread-local variable is subsequently
 * {@linkplain #get read} by the current thread, its value will be
 * reinitialized by invoking its {@link #initialValue} method,
 * unless its value is {@linkplain #set set} by the current thread
 * in the interim.  This may result in multiple invocations of the
 * {@code initialValue} method in the current thread.
 *
 * @since 1.5
 */
 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

ThreadLocalMap中的remove方法代码如下:


/**
 * Remove the entry for key.
 */
private void remove(ThreadLocal key) {
    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)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

你可能感兴趣的:(java)