ThreadLocal
1、ThreadLocal简介
ThreadLocal用来创建线程局部变量。线程局部变量是每个线程都有自己独立的一个变量副本,而这个副本对其他线程是不可见的。这意味着每一个线程都可以在不影响其他线程的情况下,修改自己线程的副本值。ThreadLocal的实例通常是在类中以静态字段的方式存在的。
ThreadLocal的主要用途是解决多线程环境下的数据同步问题,使得每个线程都拥有独立的数据副本。因此,ThreadLocal通常用于在一个线程中保存信息,而在其他线程中不需要或者不应该访问这些信息。
public class Example {
// 创建一个线程局部变量
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public static void main(String[] args) {
// 两个线程同时运行
Runnable task1 = () -> {
threadLocal.set(100); // 为每个线程设置一个值
try {
Thread.sleep(2000); // 让线程睡眠一段时间,这样我们可以看到每个线程独立地保留了自己的值
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get()); // 打印当前线程的名字和其对应的值
};
Runnable task2 = () -> {
threadLocal.set(200); // 为每个线程设置一个值
try {
Thread.sleep(1000); // 让线程睡眠一段时间,这样我们可以看到每个线程独立地保留了自己的值
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get()); // 打印当前线程的名字和其对应的值
};
new Thread(task1, "Thread-1").start();
new Thread(task2, "Thread-2").start();
}
}
在这个例子中,我们有两个线程,每个线程都设置了threadLocal的值,然后在稍后的时间打印了这个值。尽管两个线程访问的是相同的ThreadLocal实例,但是因为ThreadLocal使得每个线程都有自己的独立副本,所以它们打印出来的值是各自设置的值,而不是另一个线程的值。
2、ThreadLocal中的弱引用
对于弱引用类,当他没有被引用的时候,jvm的垃圾回收总会回收弱引用的对象。
ThreadLocal变量中的值是存储在每个线程的ThreadLocalMap中的,而这个Map中的键是ThreadLocal的弱引用。这意味着,如果ThreadLocal变量没有其他强引用指向它,那么它的值就可能会被垃圾回收器回收,尽管这个值还在被线程使用。这可能导致线程在后续访问这个值时,发现这个值已经被回收,从而引发错误。
为了避免这个问题,一种方法是确保ThreadLocal变量始终有强引用指向它。这可以通过在类中定义一个静态的ThreadLocal变量来实现。由于静态变量在类被加载时就被初始化,并且始终有强引用指向它,因此它的值不会被垃圾回收器回收。
这点上边我们已经说过了,但是使用静态的ThreadLocal还会造成另外一个问题,当线程的ThreadLocal类随gc而被销毁时,代表key的ThreadLocal被销毁了,但是他们的值还是被静态的ThreadLocal所引用,而不会被回收,如果值过多,或导致内存溢出,所以应该在线程结束的时候,调用threadLocal.remove() 方法来移除线程中ThreadLocal中的值。
示例代码:
public class Example {
// 创建一个静态的线程局部变量
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public static void main(String[] args) {
// 两个线程同时运行
Runnable task1 = () -> {
threadLocal.set(100); // 为每个线程设置一个值
try {
Thread.sleep(2000); // 让线程睡眠一段时间,这样我们可以看到每个线程独立地保留了自己的值
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get()); // 打印当前线程的名字和其对应的值
// 线程最后移除threadLocal中的值,移除的是当前线程的
threadLocal.remove()
};
Runnable task2 = () -> {
threadLocal.set(200); // 为每个线程设置一个值
try {
Thread.sleep(1000); // 让线程睡眠一段时间,这样我们可以看到每个线程独立地保留了自己的值
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get()); // 打印当前线程的名字和其对应的值
// 线程最后移除threadLocal中的值,移除的是当前线程的
threadLocal.remove()
};
new Thread(task1, "Thread-1").start();
new Thread(task2, "Thread-2").start();
}
}
在这个示例中,我们定义了一个静态的ThreadLocal变量,并在两个线程中设置了它的值。由于这个ThreadLocal变量是静态的,因此它始终有强引用指向它,它的值不会被垃圾回收器回收,每个线程都可以保留自己的值。
3、ThreadLocal类的使用场景
ThreadLocal类在Java中主要被用于创建线程局部变量。这对于需要保证每个线程都有自己独立的数据副本,而同时保证数据在各个线程间不会共享的情况非常有用。以下是一些在生产环境中可能会使用ThreadLocal的情况:
- 数据库连接:在Web应用服务器中,往往使用ThreadLocal来为每个线程存储一个数据库连接,这样每个线程都可以使用自己的数据库连接,而不会影响其他线程。
- 事务管理:在一些复杂的应用中,你可能需要在一个线程中管理多个数据库事务。通过ThreadLocal,你可以为每个线程存储事务的信息,防止事务间的互相干扰。
- 会话管理:在Web应用中,你可能需要跟踪每个用户的会话状态。通过将会话信息存储在ThreadLocal中,可以确保每个线程的会话状态都是独立的。
- 线程安全的随机数生成:Random类不是线程安全的,如果在多个线程中同时使用Random类,可能会产生竞争条件。通过在ThreadLocal中存储一个Random实例,可以为每个线程提供一个独立的随机数生成器。
- 线程安全的日期格式化:SimpleDateFormat类也不是线程安全的,如果在多个线程中同时使用SimpleDateFormat类,可能会产生竞争条件。通过在ThreadLocal中存储一个SimpleDateFormat实例,可以为每个线程提供一个独立的日期格式化工具。
- 缓存:在一些需要缓存数据的场景中,ThreadLocal可以被用来为每个线程提供一个独立的缓存,避免线程间的数据共享和污染。
4、InheritableThreadLocal
子线程继承当前父线程中存在的值。
当前父线程存在啥值,子线程就继承啥。对于同一个对象,父线程修改后,子线程可以感知到该对象的变化。子线程运行后,父线程再添加新值,子线程不会再感知
public class ExampleInheriterableThreadLocal {
private static BrokerOfThread broker = new BrokerOfThread("broker-init");
private static InheritableThreadLocal<BrokerOfThread> threadLocal = new InheritableThreadLocal<>();
static {
threadLocal.set(broker);
}
public static void main(String[] args) throws InterruptedException {
final Thread threadA = new Thread() {
@Override
public void run() {
final BrokerOfThread brokerOfThread = threadLocal.get();
while (brokerOfThread.isRun()) {
final BrokerOfThread brokerOfThread1 = threadLocal.get();
System.out.println("当前线程是" + Thread.currentThread().getName() + "----正在运行,当前的threadLocal值是-->" + brokerOfThread1.toString());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "-----线程退出");
}
};
final Thread threadB = new Thread() {
@Override
public void run() {
final BrokerOfThread brokerOfThread = threadLocal.get();
while (brokerOfThread.isRun()) {
final BrokerOfThread brokerOfThread1 = threadLocal.get();
System.out.println("当前线程是" + Thread.currentThread().getName() + "----正在运行,当前的threadLocal值是-->" + brokerOfThread1.toString());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "-----线程退出");
}
};
threadA.start();
threadB.start();
Thread.sleep(1000);
threadLocal.set(new BrokerOfThread("broker-after"));
System.out.println("main线程为InheritableThreadLocal设置了新的值");
Thread.sleep(10000);
broker.setRun(false);
Thread.sleep(10000);
}
}
class BrokerOfThread {
private String name;
private long timestamp;
private boolean run;
public BrokerOfThread(String name) {
this.name = name;
this.run = true;
this.timestamp = System.currentTimeMillis();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public boolean isRun() {
return run;
}
public void setRun(boolean run) {
this.run = run;
}
@Override
public String toString() {
return "BrokerOfThread{" +
"name='" + name + '\'' +
", timestamp=" + timestamp +
", run=" + run +
'}';
}
}