java中ThreadLocal使用

java中ThreadLocal使用

文章目录

  • java中ThreadLocal使用
      • 一、简介
      • 二、使用
      • 三、原理
        • 3.1 Thread类
        • 3.2 ThreadLocal类
          • 3.2.1 主要方法
          • 3.2.2 ThreadLocal.ThreadLocalMap内部类
          • 3.2.3 ThreadLocal.ThreadLocalMap.Entry内部类
        • 3.3 注意
      • 四、示例

一、简介

ThreadLocal是java线程中的局部变量,变量作用域仅在当前线程有效,是线程安全的。ThreadLocal常用于在多线程中,各自线程保存自己私有的变量值,如会话管理等场景。

二、使用

ThreadLocal类定义及常用方法如下:

public class ThreadLocal<T> {
  protected T initialValue() //初始化方法,protected方法,用于子类重写;
  public void set(T value) //设置值;
  public T get() //获取值;
  public void remove() //清空值。当线程回收时,局部变量也会自动回收,主动调起是非必须操作,只是加快回收速度;
}

三、原理

ThreadLocal在多线程中是线程安全的,原因是在每个线程中,都维持着自己私有的类型为ThreadLocal.ThreadLocalMap的变量threadLocals。

3.1 Thread类

Thread类关键源码:

//Thread类中threadLocals变量, 维护在ThreadLocal中
ThreadLocal.ThreadLocalMap threadLocals = null;

即是Thread类每个实例都有自己独立的threadLocal变量,理所当然线程是安全的。

3.2 ThreadLocal类

3.2.1 主要方法

TheadLocal类的主要操作方法源码如下:

//获取值
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();
}

//初始化
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;
}

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

//获取线程关联的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

//创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

分析上述操作方法,可以发现所有读写操作依赖于ThreadLocal.ThreadLocalMap内部类。

3.2.2 ThreadLocal.ThreadLocalMap内部类

ThreadLocal.ThreadLocalMap内部类,维护着每个线程自己的多个ThreadLocal变量, key为当前的threadLocal实例(ThreadLocal本身不存值,只是引用),value为设置的值 (值存放在ThreadLocal.ThreadLocalMap.Entry实例中)。关键定义如下:

static class ThreadLocalMap {
		//存储数据的数组
  	private Entry[] table;
		//获取Entry,key的类型为ThreadLocal
    private Entry getEntry(ThreadLocal<?> key) {
              int i = key.threadLocalHashCode & (table.length - 1);
              Entry e = table[i];
              if (e != null && e.get() == key)
                  return e;
              else
                  return getEntryAfterMiss(key, i, e);
    }
  }
3.2.3 ThreadLocal.ThreadLocalMap.Entry内部类

ThreadLocal.ThreadLocalMap.Entry内部类,是存储数据的实际数据结构,继承WeakReference,因此是个弱引用,当虚拟机垃圾回收时,无论内存是否足够,都会被回收。 源码如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
    / The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

到此,ThreadLocal能实现线程安全,私有,脉络很清晰了。实质是,变量存在于各个线程空间内存中,各自调自己的内存空间变量,互不影响。

3.3 注意

使用ThreadLocal要注意内存泄露,使用完后,要主动调用remove()方法。原因:

  • Entry的key是个WeakReference弱引用的ThreadLocal对象,会被垃圾回收,回收后,key会变为null,但value还存在,而又无法通过已经变为null的key索引到value,因此value所在内存便无法使用,又无法回收,导致内存泄露。
  • ThreadLocalMap(即位于Thread中的变量)threadLocals本身的生命周期与线程一致,即使ThreadLocal本身弱引用已经回收,但value还存在于ThreadLocalMap中的Entry中,导致内存泄露。

四、示例

import lombok.Data;
import org.apache.commons.lang3.RandomUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadLocalStudy {
    public static void main(String[] args) {
        //线程一,定义自己的变量001
        Runnable t1 = () -> {
            try {
                StuContext.get().setStuNo("001");
                System.out.println("t1: set stuNo 001" );
                TimeUnit.SECONDS.sleep(RandomUtils.nextInt(0, 3));
                System.out.println("t1: get " + StuContext.get().getStuNo());
            } catch (InterruptedException e) {
            }finally {
                StuContext.stuCtx.remove();
                System.out.println("t1: remove stuCtx" );
            }
        };

        //线程二,定义自己的变量002
        Runnable t2 = () -> {
            try {
                StuContext.get().setStuNo("002");
                System.out.println("t2: set stuNo 002" );
                TimeUnit.SECONDS.sleep(RandomUtils.nextInt(0, 3));
                System.out.println("t2: get " + StuContext.get().getStuNo());
            } catch (InterruptedException e) {
            }finally {
                StuContext.stuCtx.remove();
                System.out.println("t2: remove stuCtx" );
            }
        };
        //使用线程池执行线程t1,t2
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.submit(t1);
        executor.submit(t2);
        executor.shutdown();
    }

		//定义stu上下文类
    @Data
    public static class StuContext {
        public static ThreadLocal<StuContext> stuCtx = new ThreadLocal<>();
        private String stuNo;
				//获取上下文
        public static StuContext get() {
            if (null == stuCtx.get()) {
                //初始化上下文
                stuCtx.set(new StuContext());
            }
            return stuCtx.get();
        }
    }
}

输出:

t2: set stuNo 002
t1: set stuNo 001
t2: get 002
t2: remove stuCtx
t1: get 001
t1: remove stuCtx

结果分析:从上面输出可以看出,t1和t2线程在各自的线程内操作TheadLocal变量stuCtx,相互间不干扰。

你可能感兴趣的:(java多线程)