ThreadLocal源码分析

2019-05-15  本文已影响0人  barry_di

什么是ThreadLocal

ThreadLocal是一个线程独享的本地存放空间,什么是独享的本地空间,也就是时候每个线程都有一份,
线程之间不能共享该区域。主要用于存储线程变量。

什么情况下会使用ThreadLocal

多线程的情况下,如果存在资源的竞争的时候,我们通常使用锁的机制去处理这种资源的竞争,而对于一些资源
每个线程都要有一份的情况下,我们就会使用ThreadLocal.

例如:

  1. Spring的事务管理,是通过C3P0或者其他的数据库连接池中获取一个Connection,然后将Connection存放每个线程中
    的ThreadLocal中,保证每个线程之间使用的数据库连接都是独享的。我们可以想下,如果每个线程都是使用同一个
    数据连接,我们就无法控制事务的提交和回滚。因此每个线程必须独占这个数据库连接。那如果我们在Dao层
    获取Connection,然后执行SQL提交事务。就不会存在这个问题啦,其实并不是,我们通常会在三层中的服务层进行事务
    的开启和事务的提交、回滚。那么为什么会在服务层进行事务的处理,主要是因为事务的边界问题。
    因为Service会调用一系列的DAO对数据库进行多次操作。多个Dao中我们无法确定到底哪个Dao进行事务的提交。
    因此使用ThreadLocal进行存放每个数据库的连接。
  2. 还有一种场景,在多数据源的情况下,我们可以通过一个key获取对应的数据源,而这个key值
    ,从我们ThreadLocal中获取到对应的数据源。从而隔离多线程下数据源的访问。

ThreadLocal的使用

public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

 public T get() 获取当前线程的变量
 public void set(T value)  设置当前线程的变量
 public void remove() 删除线程变量

ThreadLocal源码分析

一、ThreadLocal结构分析

ThreadLocal的实现原理,我们可以通过ThreadLocal的get方法进行跟踪分析。

1    public T get() {
2        Thread t = Thread.currentThread();
3        ThreadLocalMap map = getMap(t);
4        if (map != null) {
5            ThreadLocalMap.Entry e = map.getEntry(this);
6            if (e != null) {
7                @SuppressWarnings("unchecked")
8                T result = (T)e.value;
9                return result;
10            }
11        }
12        return setInitialValue();
13    }

从ThreadLocal的get方法中我们可以看出,现获取当前的线程,再通过线程去获取
对应线程的ThreadLocalMap.从ThreadLocalMap的名字,我们可以看出他是一个Map的数据结构。
我们就想ThreadLocalMap是存放在哪里的呢?我们可以通过跟中getMap的方法中看出,ThreadLocalMap
是存放到Thread中。

1  ThreadLocalMap getMap(Thread t) {
2        return t.threadLocals;
3    }

而Thread中的ThreadLocalMap是在ThreadLocal中进行初始化。我可以通过ThreadLocal中的set方法和setInitialValue方法查看到初始化ThreadLocalMap.

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

  void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
image.png
二、ThreadLocalMap
1    static class ThreadLocalMap {
2          static class Entry extends WeakReference<ThreadLocal<?>> {
3              /** The value associated with this ThreadLocal. */
4              Object value;
5  
6              Entry(ThreadLocal<?> k, Object v) {
7                  super(k);
8                  value = v;
9              }
10          }
11
12        private Entry[] table;

那么如果我们在代码中创建两个ThreadLocal会怎样存储的呢?

 public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "hello";
        }
    };

通过对setInitialValue方法进行分析发现,我们创建两个ThreadLocal,其实内部是将当前的ThreadLocal对象作为键值,initialValue的返回值做为Value。

  private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

总结:
我们通过对ThreadLocalMap的set和get方法,可以发现,我们ThreadLocalMap是存Thread中,由ThreadLocal的设置相关方法进行创建。存储的数据结构是以ThreadLocal作为键保存对应的Value到ThreadLocalMap中。


image.png

我们可以通过源码查看到Entry是Key是一个弱引用,而不是像HashMap那样使用强引用。那么ThreadLocalMap中的key为弱引用的,那么ThreadLcaoMap的中的值只能存活到下一次的GC。

弱引用:通常用于存储非必需对象的,弱引用指向的对象实例,在下一次垃圾收集发生之前是存活的。

import org.junit.Test;


public class UseThreadLocal {
    private static int threadCount = 30;

    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "hello";
        }
    };

    class ThreadLocalRunable implements Runnable {

        private int threadId;

        public ThreadLocalRunable(int threadId) {
            this.threadId = threadId;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id  = "+threadLocal.get());
            threadLocal2 = null;
            Integer threadLocalInt = threadLocal.get();
            threadLocal.set(threadLocalInt+this.threadId);
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
        }
    }

    @Test
    public void test() throws InterruptedException {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0 ;i<threadCount;i++){
            threads[i] = new Thread(new ThreadLocalRunable(i),"线程"+i);
        }
        for (Thread t :threads){
            t.start();
            t.join();
        }

    }
}

为了防止内存泄漏,我们可以通过ThreadLocal提供的remove移除ThreadlocalMap中ThreadLocal的Value。

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

运行时设置堆内存为1m
-Xms=1m
-Xmx=1m


public class ThreadLocalOOM {
    private static int threadCount = 30;

    public static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>() {
        @Override
        protected HashMap initialValue(){
            HashMap map= new HashMap<>();
            for (int i=0;i<1000000000;i++){
                map.put(i,i);
            }
            return map;
        }
    };



    class ThreadLocalRunable implements Runnable {

        private int threadId;

        public ThreadLocalRunable(int threadId) {
            this.threadId = threadId;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id  = "+threadLocal.get());
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
        }
    }
    @Test
    public void test() throws InterruptedException {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0 ;i<threadCount;i++){
            threads[i] = new Thread(new ThreadLocalRunable(i),"线程"+i);
        }
        for (Thread t :threads){
            t.start();
            t.join();
        }

    }
}
上一篇 下一篇

猜你喜欢

热点阅读