ThreadLocal是什么?

ThreadLocal叫做线程变量,它是一个线程的本地变量,意味着这个变量是线程独有的,是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。

ThreadLocal 的 API

public API描述
set(T)设置当前线程的副本
T get()获取当前线程的副本
void remove()移除当前线程的副本
ThreadLocal<S> withInitial(Supplier<S>)创建 ThreadLocal 并指定缺省值创建工厂
protected API描述
T initialValue()设置缺省值

代码示例

案例1

在以下这个代码示例中我们发现,如果多个线程共用一个SimpleDateFormat对象的话,实际运行下来会出现问题。但是通过ThreadLocal,我们为每个线程分别创建了一个SimpleDateFormat,就不会出现什么问题了。

在多线程环境中,将字符串转换为日期对象。发现多个线程使用同一个SimpleDateFormat对象产生了线程安全问题,有的线程报了错误:

public class Test2 { //定义SimpleDataFoemat对象,可以将字符串转换为日期 //发现多个线程使用同一个SimpleDateFormat对象,会产生线程安全问题 // private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); //使用ThreadLocal,为每个线程创建一个SimpleDateFormat static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>(); //定义Runnable接口的实现类 static class ParseDate implements Runnable { private int i = 0; public ParseDate(int i) { this.i = i; } @Override public void run() { String text = "2068年11月22日 08:22:" + i % 60; //构建日期字符串 try { if(threadLocal.get() == null) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); threadLocal.set(sdf); } SimpleDateFormat sdf = threadLocal.get(); Date date = sdf.parse(text); System.out.println(i + "--" + date); } catch (ParseException e) { e.printStackTrace(); } } } public static void main(String[] args) { //创建100个线程 for(int i = 0; i < 100; i++) { new Thread(new ParseDate(i)).start(); } } } 

 案例2:ThreadLocal指定初始值

定义ThreadLocal的子类,在子类中重写initialValue()方法指定初始值

public class Test3 { static class SubThreadLocal extends ThreadLocal<Date> { @Override protected Date initialValue() { return new Date(); } } //定义ThreadLocal对象 static ThreadLocal<Date> threadLocal = new SubThreadLocal(); //定义线程类 static class SubThread extends Thread { @Override public void run() { for(int i = 0; i < 10; i++) { //第一次调用threadLocal的get方法,会返回null System.out.println("----" + Thread.currentThread().getName() + " value = " + threadLocal.get()); if(threadLocal.get() == null) { System.out.println("-------"); threadLocal.set(new Date()); } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { SubThread t1 = new SubThread(); t1.start(); TimeUnit.SECONDS.sleep(1); SubThread t2 = new SubThread(); t2.start(); } } 

ThreadLocal有哪些使用场景?

  • 场景 1 – 无锁线程安全: ThreadLocal 提供了一种特殊的线程安全方式,从根本上避免资源竞争,也体现了空间换时间的思想;
  • 场景 2 – 线程级别单例: 一般的单例对象是对整个进程可见的,使用 ThreadLocal 也可以实现线程级别的单例;
  • 场景 3 – 共享参数: 如果一个模块有非常多地方需要使用同一个变量,相比于在每个方法中重复传递同一个参数,使用一个 ThreadLocal 全局变量也是另一种传递参数方式。
  • 1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  • 2、线程间数据隔离,【你访问你的,我访问我的,不会相互干扰】
  • 3、进行事务操作,用于存储线程事务信息。
  • 4、数据库连接,Session会话管理。
  • 5、MDC链路追踪。

总之一句话就是你想每个线程需要有自己单独的实例或者实例需要在多个方法中共享,但不希望被多线程共享

Threadlocal线程共享?注意事项

ThreadLocal变量,即线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
  • ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。
  • ThreadLocal 变量通常被 private static修饰。当一个线程(Thread)结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

Threadlocal会内存泄漏吗?

由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。

但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

ThreadLocal正确的使用方法

1)每次使用完ThreadLocal都调用它的remove()方法清除数据

2)将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

总结

以上就是Java多线程:ThreadLocal详解的全部内容!