提供一个变量的副本,线程的隔离。

  spring在事务的部分用到了ThreadLocal,让每个线程保存自己的连接,不在同一连接的话不会形成事务

public class UserThreadLocal {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        //可以在new ThreadLocal的时候进行初始化
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };
    private static ThreadLocal<String> threadLocalString = new ThreadLocal<String>();

    private static class NewThread implements Runnable{
        int id;
        public NewThread(int id){
            this.id = id;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"---start");
            Integer i = threadLocal.get();
            i = i+id;
            threadLocal.set(i);
            threadLocalString.set(Thread.currentThread().getName());
            System.out.println(Thread.currentThread().getName()+"---"+threadLocal.get());
            System.out.println(Thread.currentThread().getName()+"---"+threadLocalString.get());
            //threadLocal.remove();
        }
    }

    public static void main(String[] a){
        Thread[] threads = new Thread[5];
        for(int i=0;i<threads.length;i++){
            threads[i] = new Thread(new NewThread(i));
        }
        for(int i=0;i<threads.length;i++){
            threads[i].start();
        }
    }

}
/* 输出:
Thread-0---start
Thread-3---start
Thread-0---1
Thread-4---start
Thread-2---start
Thread-1---start
Thread-1---2
Thread-1---Thread-1
Thread-2---3
Thread-4---5
Thread-0---Thread-0
Thread-3---4
Thread-4---Thread-4
Thread-2---Thread-2
Thread-3---Thread-3
可以保存每个变量的副本
*/

无论是get还是set 都会先拿到调用方法的线程,再获取当前线程的数据

threadLocal原理

每个线程有自己的成员变量,这个变量为ThreadLocalMap

static class ThreadLocalMap {

        /**
         * 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<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
           
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;//每一个线程的ThreadLocal可能有多个,比如上述代码中的threadLocal和threadLocalString
}

 

每一个线程都会启动一个自己的ThreadLocalMap,以ThreadLocal为键,以内容为值,存入到Entry数组

不论get还是set 都是操作自己线程的东西,与多线程无关

ThreadLocal会引发内存泄露以及线程不安全

1.内存泄露

ThreadLocal原理图:

ThreadLocal原理

一个Thread中只有一个ThreadLocalMap,一个ThreadLocalMap中可以有多个ThreadLocal对象,其中一个ThreadLocal对象对应一个ThreadLocalMap中一个的Entry(也就是说:一个Thread可以依附有多个ThreadLocal对象)。
 

在Entry里用了WeakReference(弱引用),一旦发生gc,线程的ThreadLocal会被回收,栈对象就会指到null,

当发生GC的时候,ThreadLocal被回收,ThreadLocalRef则指向null,因为Entry的key为ThreadLocal,所以Entry失去了key,

每一个线程内部(CurrentThread)都有自己的ThreadLocalMap,ThreadLocalMap持有自己的entry,entry持有自己的value,但是value要通过key访问,即ThreadLocal进行访问,所以在Gc之后,key消失了,所以value就无法被访问到了(上面这一系列为强引用),因此就发生了内存泄露,只有当线程被回收之后,本线程泄露的内存才会被回收。

ThreadLocal中的get和set以及remove都有用来清除key为null的value值的方法,但是get和set并不会清除的太及时,因此发生内存泄露的程度会减轻

使用虚引用的会在执行get set和GC的时候删除用不到的value,但是强引用的话,只有在线程结束以及主动remove的时候才会清除,加重了内存泄露

用完及时remove掉可避免

   强引用   new对象为强引用,强引用存在的时候,gc不会回收

   软引用   softReference 如果将要发生内存溢出,回收后仍不够用的话,会对堆里软引用的对象进行回收,比弱引用略强

   弱引用   只要垃圾回收,就一定会回收

   虚引用   没什么卵用,比弱引用更弱

2.线程不安全

public class ThreadLocalUnsafe implements Runnable {
    //此处用的静态Number对象
    public static  Number number = new Number(0);
    //ThreadLocal实际持有的是对象的引用,因为对象是共享的,
    //所以每个线程的ThreadLocal持有的是同一个对象的实例的不同副本,所以结果并不是预想中的1
    
    //解决方案  1:去掉static ,让每个线程有自己独立的Number实例
    //          2:不再单独定义number,在创建ThreadLocal的时候设置初始值,如:
    /*public static ThreadLocal<Number> values = new ThreadLocal<Number>(){
        @Override
        protected Number initialValue() {
            return new Number(0);
        }
    };*/
    public static ThreadLocal<Number> values = new ThreadLocal<>();

    @Override
    public void run() {
        number.setNum(number.getNum()+1);
        values.set(number);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"-----"+values.get());
    }

    public static void main(String[] a){
        for(int i=0;i<5;i++){
            new Thread(new ThreadLocalUnsafe()).start();
        }
    }

    private static class Number{
        private int num;
        public Number(int num){
            this.num = num;
        }
        public int getNum(){
            return num;
        }
        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public String toString() {
            return "Number{" +
                    "num=" + num +
                    '}';
        }
    }
}

面试:ThreadLocal 实现

最后修改于 2019-08-20 22:00:28
如果觉得我的文章对你有用,请随意赞赏
扫一扫支付
上一篇