ThreadLocal

简介

  我们都知道,多线程情况下,是会有资源竞争问题。当并非访问某共享资源时,就会出现问题,尤其是写操作,程序猿一般通过同步锁机制来保证线程安全。而当我们需要为每一个线程都保存一份线程独有的数据时,即相当于将共享变量变为每个线程都有一份的私有变量,就可以使用到ThreadLocal。
  ThreadLocal位于java.lang包下,是JDK提供的一个类,支持泛型的。该类的作用就如其名字一样,线程变量。意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

特点

  • TheadLocal可以为当前线程关联一个数据,像map一样进行存取。
  • 每一个ThreadLocal对象,只能为当前线程关联一个数据,如果要关联多个,请创建多个ThreadLocal对象实例。
  • 一般将ThreadLocal对象实例定义为static类型。
  • ThreadLocal中保存数据,在线程销毁后,会由JVM自动释放。

举例

这里创建一个ThreadLocal对象,里面填充Integer类型的数据。并且创建三个线程去同时访问该对象中的数据。

public class ThreadLocalTest {

    private static ThreadLocal<Integer> local = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            local.set(10);
            System.out.println(Thread.currentThread().getName() + ":" + local.get());
            local.remove();
        }).start();
        new Thread(() -> {
            local.set(20);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + local.get());
            local.remove();
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            local.set(30);
            System.out.println(Thread.currentThread().getName() + ":" + local.get());
            local.remove();
        }).start();
    }

}

可以看到,三个线程中的num变量是隔离的,独立的。
ThreadLocal_第1张图片

原理分析

ThreadLocall是如何给线程保存变量的呢?
  ThreadLocal类中有一个静态的内部类ThreadLocalMap,通过操作ThreadLocalMap的get和set放来来实现数据的存储,数据就存储在Entry[]数组中,初始长度为16。这个Entry类,是ThreadLocalMap的静态内部类。

static class Entry extends WeakReference<ThreadLocal<?>> { // Entry 是弱引用
            /** The value associated with this ThreadLocal. */
            Object value;

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

set()方法

public void set(T value) {
        Thread t = Thread.currentThread(); // 获取当前线程
        ThreadLocalMap map = getMap(t);  // 获取当前线程的map
        if (map != null) // 不为null 
            map.set(this, value); // 设置新值
        else
            createMap(t, value); // 为null,则创建一个ThreadLocalMap
    }

get()方法

public T get() {
        Thread t = Thread.currentThread(); // 获取当前线程
        ThreadLocalMap map = getMap(t); // 获取当前线程的map
        if (map != null) { // 不为null
            ThreadLocalMap.Entry e = map.getEntry(this); // 获取存储数据的entry对象
            if (e != null) { // 不为null 
                @SuppressWarnings("unchecked")
                T result = (T)e.value; // 强转
                return result;  // 返回entry的value
            }
        }
        return setInitialValue(); // 返回null
    }

内存泄漏问题

  TreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被 GC 回收,这样就会导致ThreadLocalMap中key为null,而value还存在着强引用(value可能会被其他线程所引用),只有thead线程退出以后,value的强引用链条才会断掉。但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:

ThreadRef->Thread-> ThreaLocalMap->Entry ->value 永远无法回收,造成内存泄漏。

  • key 使用强引用
     当 ThreadLocalMap 的 key 为 强 引 用 回 收 ThreadLocal 时 , 因 为ThreadLocalMap 还持有ThreadLocal 的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
  • key 使用弱引用
     当 ThreadLocalMap 的 key 为 弱 引 用 回 收 ThreadLocal 时 , 由 于ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。当 key 为 null,在下一次 ThreadLocalMap 调用set(),get(),remove()方法的时候会被清除value值。

ThreadLocal 正确的使用方法:
每次使用完ThreadLocal都调用它的remove()方法清除数据。

你可能感兴趣的:(JAVA基础,java,多线程,内存泄漏,ThreadLocal)