参考文献

ThreadLocal

它运行你将每个线程与持有数值的对象关联在一起.ThreadLocal提供了getset访问器,为每个使用它的线程维护一份单独的拷贝.所以get总是返回由当前执行线程通过set设置的最新值.

  • 一种解决多线程环境下成员变量的问题的方案,但是与线程同步无关.其思路是为每一个线程创建一个单独的变量副本,从而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本
  • 线程本地(ThreadLocal)变量通常用与基于可变的单体(Singleton)或全局变量的设计中,出现(不正确的)共享.

    比如说,一个单线程的应用程序可能会维护一个全局的数据连接,这个Connection在启动时就已经被初始化了.这样就可以避免为每个方法都传递一个Connection.因为JDBC规范并未要求Connection自身一定是线程安全的.通过利用ThreadLocal存储JDBC连接,每个线程都会拥有属于自己的Connection.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
    public Connection initialValue() {
    return DriverManager.getConnection(DB_URL);
    }
    }

    public static Connection getConnection() {
    return connectionHolder.get();
    }
  • 注意: ThreadLocal 不是用于解决共享变量的问题的,也不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制.

  • ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap(线程本地变量,线程私有,K-V型),key为使用弱引用的ThreadLocal实例,value为线程变量的副本.

  • 四个方法

    • get():返回此线程局部变量的当前线程副本中的值
    • initialValue():返回此线程局部变量的当前线程的“初始值”
    • remove():移除此线程局部变量当前线程的值
    • set(T value):将此线程局部变量的当前线程副本中的值设置为指定值

ThreadLocalMap

  • 实现线程隔离机制的关键

  • 每个Thread内部都有一个ThreadLocal.ThreadLocalMap类型的成员变量,该成员变量用来存储实际的ThreadLocal变量副本.

  • 提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本

  • 注意点

    • ThreadLocal实例本身是不存储值,它只是提供了一个在当前线程中找到副本值得key
    • ThreadLocal包含在Thread中,而不是Thread包含在ThreadLocal
  • ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时, Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMapkeynull, 而value还存在着强引用,只有Thead线程退出以后,value的强引用链条才会断掉,但如果当前线程再迟迟不结束的话,这些keynullEntryvalue就会一直存在一条强引用链.

内存泄漏问题

key 使用强引用

  • ThreadLocalMapkey为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏.

key 使用弱引用

  • ThreadLocalMapkey为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收.当keynull,在下一次ThreadLocalMap调用remove()方法的时候会被清除value值.
    • ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用.

ThreadLocal正确的使用方法

  • 每次使用完ThreadLocal都调用它的remove()方法清除数据
  • ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entryvalue值,进而清除掉 .

FastThreadLocal

  • FastThreadLocal适用于高并发场景下需要频繁使用ThreadLocal对象的情况.与传统的ThreadLocal相比,FastThreadLocal具有更高的访问速度和更小的内存占用.

使用场景

  1. 高并发场景下频繁使用ThreadLocal对象的情况;
  2. 对性能有较高要求的场景,通过使用 FastThreadLocal可以提高访问速度,减少竞争;
  3. 大规模系统中需要访问ThreadLocal对象,通过使用 FastThreadLocal可以减少内存占用.

注意点

  1. FastThreadLocal的 API 接口与ThreadLocal相似,但是并不兼容;
  2. FastThreadLocal创建的对象不能被其他线程所共享;
  3. FastThreadLocal对象的初始化可以在 get 方法中完成,这样可以减少对象创建的数量;
  4. 由于 FastThreadLocal对象并不是被线程本地引用的,因此需要手动释放对象的资源,否则可能会导致内存泄漏;
  5. FastThreadLocal对象的数量需要谨慎控制,过多的对象会占用过多的内存,并且会增加垃圾回收的复杂度.
  6. FastThreadLocal只能在FastThreadLocalThread线程中使用,这是因为FastThreadLocalThread是由FastThreadLocal`创建的一种线程类型,它可以提供更快速的线程局部变量访问和更少的竞争.
    • 在标准Thread线程中使用FastThreadLocal不是明智之举,因为FastThreadLocalThread是在FastThreadLocal类的设计中存在的,同时FastThreadLocalThread还提供了一些其他相关的功能.在标准Thread线程中使用FastThreadLocal可能会导致性能瓶颈,并且在代码中也容易引起混淆和不必要的麻烦.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package cn.holelin.netty;

import io.netty.util.concurrent.FastThreadLocal;

/**
* Netty FastThreadLocal保存的本地变量
* 只有在 FastThreadLocalThread 线程中调用才会使用FastThreadLocal的逻辑,否则会使用ThreadLocal
*/
public class FastContext {

private static final FastThreadLocal<FastContext> FAST_THREAD_LOCAL = new FastThreadLocal<FastContext>() {
@Override
protected FastContext initialValue() {
return new FastContext();
}
};

public static FastContext currentContext() {
return FAST_THREAD_LOCAL.get();
}

public static void remove() {
FAST_THREAD_LOCAL.remove();
}

/**
* 线程追踪ID
*/
private String thraceId;

public String getThraceId() {
return thraceId;
}

public void setThraceId(String thraceId) {
this.thraceId = thraceId;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package cn.holelin.netty;

import io.netty.util.concurrent.DefaultThreadFactory;

import java.util.UUID;

public class FastThreadLocalTest {
public static void main(String[] args) {
// Netty提供的DefaultThreadFactory创建的线程是FastThreadLocalThread类型的
DefaultThreadFactory NettyFastThreadFactory = new DefaultThreadFactory("FastThreadTest");
NettyFastThreadFactory.newThread(() -> {
FastContext fastContext = FastContext.currentContext();
String uuid = UUID.randomUUID().toString();
System.out.println(uuid);
fastContext.setThraceId(uuid);

testFastThreadLocal();
}).start();

}

private static void testFastThreadLocal() {
FastContext fastContext = FastContext.currentContext();
System.out.println(fastContext.getThraceId());
FastContext.remove();
}
}

ThreadLocal,InheritThreadLocal,阿里的TransmittableThreadLocal

  • ThreadLocal可以用来解决什么问题,你工作中或者使用的框架中哪里用到了?
    • ThreadLocal主要用于解决多线程环境下共享变量的问题,它可以为每个线程创建一个独立的变量副本,使得每个线程都可以独立地操作自己的变量副本而不会相互干扰.
    • 使用ThreadLocal来管理用户请求的上下文信息,比如用户的身份认证信息、请求的语言环境等.这样可以确保在多个线程同时处理不同用户请求时,上下文信息不会混乱.
  • ThreadLocal解决不了什么问题?怎么办
    • ThreadLocal无法解决跨线程传递变量的问题,即如果在一个线程中创建了一个新的线程,那么无法直接将ThreadLocal中的值传递给子线程.要解决这个问题,可以使用InheritableThreadLocal.InheritableThreadLocalThreadLocal的子类,它可以实现将父线程中的ThreadLocal值传递给子线程.通过InheritableThreadLocal,可以在父子线程之间传递上下文信息.
  • InheritThreadLocal解决不了什么问题,怎么办
  • 阿里的TransmittableThreadLocal是如何解决这个问题的
    • 阿里的TransmittableThreadLocal通过字节码增强的方式,修改了线程池的代码,使得在线程池创建线程时,对RunnableCallable进行包装,并将需要传递的ThreadLocal值存储到包装类中.当线程执行包装类中的逻辑时,首先将包装类中保存的ThreadLocal值重新放入ThreadLocal中,然后再执行真正的业务逻辑.这样就保证了在线程池中正确传递ThreadLocal的值,解决了InheritableThreadLocal无法处理线程池重用线程的问题.