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: <br>
 *
 * @author: name:yuxin 
 * Create Time:  2018/5/28 0028-下午 10:10<br>
 */
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: <br>
 *
 * @author: name:yuxin
 * Create Time:  2018/5/28 0028-下午 10:10<br>
 */
public class TlTest {
    private static ThreadLocal<SimpleDateFormat> 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;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值