ThreadLocal使用详解

1、ThreadLocal的使用场景

为线程中一个本地变量的副本提供索引,ThreadLocal能够用来维护与当前线程相关的一些上下文,不须要经过每一个方法调用将其做为参数传递。bash

使用threadLocal必定要注意内存泄漏,不然仍是建议定义context类,保存每一个线程自身上下文多线程

2、ThreadLocal分析

API
四个主要方法:优化

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
复制代码
  • 每个线程均可以独立地改变本身的副本,而不会影响其余线程所对应的副本。ui

  • 内部存在一个ThreadLocalMap,key为ThreadLocal自身,值为线程局部变量spa

  • 初始时,在Thread里面,threadLocals(ThreadLoaclMap的实例)为空,当经过ThreadLocal变量调用get()方法时,就会对Thread类中的threadLocals进行初始化,而且以当前ThreadLocal变量为key,使用默认初始化的value做为值,存到threadLocals。线程

注意: 这里初始化的value默认是null,若是get以前没有调用set方法,会报空指针异常,咱们建立ThreadLocal变量是能够重写initialValue()方法。3d

private static ThreadLocal<String> strThreadLocal = new ThreadLocal<String>() {
        public String initialValue() {
            return "default";
        }
    };
复制代码

3、ThreadLocal内存泄漏

tips:
四种类型的引用指针

  1. 强引用
    new 对象
    Object obj = new Object();
    正常垃圾回收code

  2. 软引用
    内存空间不够,垃圾回收时会回收其内存
    Object obj = new Object();
    SoftReference sf = new SoftReference(obj);
    obj = null;
    sf.get();//有时候会返回nullcdn

  3. 弱引用
    弱引用的对象,能够存活到下一次垃圾回收
    Object obj = new Object();
    WeakReference wf = new WeakReference(obj);
    obj = null;
    wf.get();//有时候会返回null
    wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾

  4. 虚应用
    不影响对象的生命周期。
    虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之关联的引用队列中。
    若是程序发现某个虚引用已经被加入到引用队列,那么就能够在所引用的对象的内存被回收以前采起必要的行动。

  5. 内存泄漏: 原本应该被回收的对象由于某种缘由没法被回收

    ThreadLocal 中的ThreadLocMap(后面简称为map),它会在线程运行的整个周期都会存在,若是map中的key和value是强引用,那么map持有它们的引用,引用的ThreadLocal的对象被回收了,可是整个Entry仍是存在,形成内存泄漏。

    对此,Threadlocal有如下优化:

    1. map中key使用弱引用,在一次gc后,能够会被自动回收,变成null
    2. 每次调用set,get,remove方法会自动清除value对应为空的对象

    在多线程不断set放入一个大对象的时候,其中一个线程运行周期比较长,在这期间没有发生gc,ThreadLocalMap中的key就不会及时回收,或者程序运行异常中断,set以后不再会调用threadLocal相关方法,都会形成内存泄漏。

    为了不内存泄漏,建议使用remove方法:

    //伪代码
    try {
        threadLocal.set()
        业务逻辑...
        threadLocal.get()
    } catch(){
    } finally{
        threadLocal.remove()
    }
    复制代码

    简单代码示例:

    public class Test {
        ThreadLocal<String> stringLocal = new ThreadLocal<String>() {
            public String initialValue() {
                return "default";
            }
        };
        ThreadLocal<Long> longLocal = new ThreadLocal<Long>() {
            public Long initialValue() {
                return 0L;
            }
        };
    
        public void set() {
            longLocal.set(Thread.currentThread().getId());
            stringLocal.set(Thread.currentThread().getName());
        }
    
        public void get() {
            System.out.println("longLocal: " + longLocal.get());
            System.out.println("stringLocal: " + stringLocal.get());
        }
    
        public static void main(String[] args) throws InterruptedException {
            final Test test = new Test();
            System.out.println("获取默认值");
            test.get();
            System.out.println("----------");
            System.out.println("主线程:");
            test.set();
            test.get();
            System.out.println("----------");
            //开启一个线程
            Thread t1 = new Thread() {
                public void run() {
                    System.out.println("t1线程:");
                    test.set();
                    test.get();
                }
            };
    
            t1.start();
            t1.join();
            System.out.println("-----------");
            System.out.println("验证主线程:");
            test.get();
            //在主线程和t1线程中,各自建立了一个变量副本,stringLocal ,longLocal不同
        }
    }
    
    
    结果:  
    获取默认值
    longLocal: 0
    stringLocal: default
    ----------
    主线程:
    longLocal: 1
    stringLocal: main
    ----------
    t1线程:
    longLocal: 11
    stringLocal: Thread-0
    -----------
    验证主线程:
    longLocal: 1
    stringLocal: main
    复制代码
相关文章
相关标签/搜索