面试题:
1、ThreadLocal 与 ThreadLocalMap 的数据结构和关系?
2、ThreadLocal 的 key 是弱引用,这是为什么?
3、ThreadLocal 内存泄漏问题你知道吗?
4、ThreadLocal 中最后为什么要加 remove 方法?
一、ThreadLocal 简介
1、ThreadLocal 是什么?
ThreadLocal 能提供线程局部变量,这些变量与正常的变量不同,因为每一个线程在访问 ThreadLocal 实例的时候(通过 get 或 set 方法)都有自己独立初始化的变量副本,ThreadLocal 实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户 ID 或者事务 ID)关联起来。
2、ThreadLocal 能干什么?
ThreadLocal 能实现每一个线程都有自己专属的本地变量副本,主要解决了让每个线程绑定自己的值,通过使用 get()和 set() 方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全问题。
3、ThreadLocal 常用的 API 介绍
/**
返回该线程局部变量在当前线程副本中的值。如果该变量对于当前线程没有值,它首先被初始化调用 initialValue 方法得到返回的值。
*/
public T get() {
}
/**
返回当前线程的这个线程局部变量的“初始值”。该方法将在线程第一次使用 get 方法访问变量时被调用
除非线程之前调用了set 方法,在这种情况下,initialValue 方法将不会被线程调用。
通常,这个方法在每个线程中最多调用一次,但是在后续调用 remove 和 get 的情况下,它可能会被再次调用。
*/
protected T initialValue() {
return null;
}
/**
删除当前线程局部变量的值。如果这个线程局部变量随后被当前线程调用了 get ,它的值将通过调用它的 initialValue 方法重新初始化,除非它的值在过渡期间被当前线程调用了 set 。这可能导致在当前线程中多次调用 initialValue 方法。
*/
public void remove() {
}
/**
将当前线程的这个线程局部变量的副本设置为指定的值。大多数子类将不需要覆盖这个方法,仅仅依靠 initialValue 方法来设置线程局部变量的值
*/
public void set(T value) {
}
/**
创建线程局部变量。变量的初始值是通过方法上 Supplier 的 get 方法来确定的。jdk1.8 才有的。
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
}
二、ThreadLocal 简单使用
1、问题引出
首先,看一个卖票的场景,代码如下:
public class Code_19_ThreadLocalTest {
public static void main(String[] args) {
MovieTicket movieTicket = new MovieTicket();
new Thread(() -> {
for(int i = 0; i < 3; i++) {
movieTicket.saleTicket();}}, "t1").start();
new Thread(() -> {
for(int i = 0; i < 3; i++) {
movieTicket.saleTicket();}}, "t2").start();
new Thread(() -> {
for(int i = 0; i < 3; i++) {
movieTicket.saleTicket();}}, "t3").start();
try {
Thread.sleep(300); } catch (InterruptedException e) {
e.printStackTrace(); }
System.out.println("****总卖出去的票:" + movieTicket.ticketNumber);
}
}
class MovieTicket {
public int ticketNumber = 0;
public synchronized void saleTicket() {
System.out.println(Thread.currentThread().getName() + "\t 卖出第:" + (++ticketNumber) + "票");
}
}
如上代码,使用 3 个线程进行卖票,由于 ticketNumber 是每个线程共享的资源,存在线程安全问题,所以使用 synchronized 加锁,上面的代码存在的问题是,多个线程只有一把锁,只有当上一个线程释放锁,才有机会拿到锁,这是因为多个线程使用的是一个共享的变量,如果说每个线程都有自己的本地副本变量,那么也就不会存在线程安全问题,是一种不加锁的方式解决线程安全问题。
2、使用 ThreadLocal 解决
public class Code_19_ThreadLocalTest {
public static void main(String[] args) {
MovieTicket movieTicket = new MovieTicket();
new Thread(() -> {
movieTicket.run(3);
}, "t1").start();
new Thread(() -> {
movieTicket.run(