ThreadLocal结构和用法

1、ThreadLocal的数据结构

    ThreadLocal内部维护的是一个类似Map的ThreadLocalMap数据结构,key为当前对象的Thread对象,值为泛型的Object。使用ThreadLocal比较方便的就是当thread不变的情况下,可以很方便的设置或者获取对象。但是需要注意的是ThreadLocal中的Entry的key和value的关系有系统进行维护,若维护不当则可能导致多线程状态下的不安全(一般不会,至少需要注意)。

ThreadLocal结构和用法_第1张图片

    对于具体的使用,需要注意其实现方法,下面是基于java8的实现方式:

    get方法:

    /**
     * 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) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    set方法 和 创建Map方法:

    /**
     * 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);
    }
  

 

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

  内置的ThreadLocalMap对象:

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

ThreadLocalMap的内置Entry是WeakReference(弱引用):

/**
 * 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> {
    /** The value associated with this ThreadLocal. */
    Object value;

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

2、ThreadLocal的使用

    ThreadLocal的应用场景非常的多:

1)、之前项目中对于数据库链接池的管理

2)、在web项目中,会遇到很多情况下将数据存储到用户的session域中,但是则后面每个地方需要获取数据的时候,需要将request对象在controller层传入service层,可能会传入util方法中,或者传入其他相关处理的类中。然而这都是服务器的用户线程中,所以可以将需要的数据放入ThreadLocal中,在需要的地方之间进行获取即可。

3)、很多时候在Spring的Controller中使用非线程安全的容器或工具(经常会使用SimpleDateFormat,但这个还可以使用jota的DateTimeFormatter,可参见安全的SimpleDateForma)时,由于spring默认是单例,所以多线程的情况下线程非安全或者会照成错误,所以也可以使用ThreadLocal。

 

3、ThreadLocal使用后需要释放吗

    ThreadLocal的使用非常的方便,但是需要使用显示的将对象置为null,让其被gc,或者需要让gc不可达吗?至少在1.7,1.8中,由于ThreadLocal中的内置对象的ThreadLocalMap的Entry使用WeakReference(弱引用)方式,不需要显示的进行释放。如下:

package com.kevin.demo.concurrent;

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

public class ThreadLocalDemo {

    public static final int CONCURRENT = 1000;

    static volatile ThreadLocal threadLocal = new ThreadLocal(){
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println(this.toString() + "is gc !");
        }
    };

    static volatile CountDownLatch latch = new CountDownLatch(CONCURRENT);

    public static class ParseDate implements Runnable {
        int i = 0;

        public ParseDate(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            try {
                if (threadLocal.get() == null) {
                    threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
                        @Override
                        protected void finalize() throws Throwable {
                            super.finalize();
                            System.out.println(this.toString() + " is gc !");
                        }
                    });
                    System.out.println(Thread.currentThread().getId() + " created SimpleDateFormat ! ");
                }
                Date parseDate = threadLocal.get().parse("2018-07-25 23:00:00");
            } catch (ParseException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new ParseDate(i));
        }
        latch.await();
        System.out.println(" mission complete !");
        threadLocal = null;
        System.gc();
        System.out.println(" first gc is complete ! ");

        threadLocal = new ThreadLocal();
        latch = new CountDownLatch(CONCURRENT);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new ParseDate(i));
        }
        latch.await();
        Thread.sleep(1000);
        System.gc();
        System.out.println(" second gc is complete ! ");
    }

}

    我使用的jdk 1.8,执行结果如下:

F:\Java_jdk\jdk_8\jdk1.8.0_45\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:55622,suspend=y,server=n -javaagent:C:\Users\Administrator\.IntelliJIdea2018.1\system\captureAgent\debugger-agent.jar=file:/C:/Users/Administrator/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath "F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\charsets.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\deploy.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\access-bridge-32.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\dnsns.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\jaccess.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\localedata.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\nashorn.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\sunec.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\zipfs.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\javaws.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\jce.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\jfr.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\jfxswt.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\jsse.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\management-agent.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\plugin.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\resources.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\rt.jar;G:\idel_workspace\kevin-demo\out\production\kevin-demo;C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.5\lib\idea_rt.jar" com.kevin.demo.concurrent.ThreadLocalDemo
Connected to the target VM, address: '127.0.0.1:55622', transport: 'socket'
18created SimpleDateFormat ! 
17created SimpleDateFormat ! 
19created SimpleDateFormat ! 
14created SimpleDateFormat ! 
20created SimpleDateFormat ! 
15created SimpleDateFormat ! 
16created SimpleDateFormat ! 
11created SimpleDateFormat ! 
13created SimpleDateFormat ! 
12created SimpleDateFormat ! 
 mission complete !
com.kevin.demo.concurrent.ThreadLocalDemo$1@585f8ais gc !
 first gc is complete ! 
17created SimpleDateFormat ! 
second gc is complete ! 
Disconnected from the target VM, address: '127.0.0.1:55622', transport: 'socket'

Process finished with exit code -1

 

 

你可能感兴趣的:(Java_规范)