面试过程中,问到了使用ThreadLocal时要注意的事项。当时脑袋一懵,只是知道ThreadLocal使得变量线程局部化,每个线程都有自己的变量,从而使得线程安全。
下面说一些ThreadLocal使用时要注意的问题。
线程池使用过程中,ThreadLocal读取到的变量可能是上次任务执行完成的脏数据。
实例代码如下:
package com.hao.laker.study.myconcurrent;
import java.util.concurrent.*;
/**
* Created by haojiahong on 17/5/7.
*/
public class ThreadLocalTest {
private static ExecutorService executor = Executors.newFixedThreadPool(1);
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
//线程池中线程通过一个任务对ThreadLocal变量赋值。
private static class SetTask implements Callable<String> {
private Integer i;
public SetTask(Integer i) {
this.i = i;
}
@Override
public String call() throws Exception {
threadLocal.set(Thread.currentThread().getName() + "的值为:" + i);
return threadLocal.get();
}
}
//线程池中线程通过一个任务获取此线程本地化的ThreadLocal变量值
private static class GetTask implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return threadLocal.get();
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
SetTask setTask = new SetTask(2);
GetTask getTask = new GetTask();
//给线程池中的这个线程设置了ThreadLocal值
Future<String> result = executor.submit(setTask);
System.out.println(result.get());
//新的任务拿到了以前任务设置的值,这是脏数据。
Future<String> getResult = executor.submit(getTask);
System.out.println(getResult.get());
threadLocal.set(Thread.currentThread().getName() + "的值为:" + 5);
System.out.println(threadLocal.get());
Future<String> getResult2 = executor.submit(getTask);
System.out.println(getResult2.get());
}
}
package com.hao.laker.study.myconcurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by haojiahong on 17/5/23.
*/
public class ThreadLocal2Test {
private static ExecutorService executor = Executors.newFixedThreadPool(1);
public static class MyRunnable implements Runnable {
private ThreadLocal threadLocal = new ThreadLocal();
@Override
public void run() {
System.out.println(Thread.currentThread());
threadLocal.set((int) (Math.random() * 100D));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread());
System.out.println(threadLocal.get());
}
}
public static class MyRunnable2 implements Runnable {
private ThreadLocal threadLocal = new ThreadLocal();
@Override
public void run() {
System.out.println(Thread.currentThread());
threadLocal.set((int) (Math.random() * 100D));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread());
System.out.println(threadLocal.get());
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
// Thread thread1 = new Thread(sharedRunnableInstance);
// Thread thread2 = new Thread(sharedRunnableInstance);
// thread1.start();
// thread2.start();
executor.execute(sharedRunnableInstance);
executor.execute(sharedRunnableInstance);
sharedRunnableInstance = null;
System.gc();
Thread.sleep(1000);
MyRunnable2 shared2Instance = new MyRunnable2();
executor.execute(shared2Instance);
}
}
当ThreadLocal对象存储在各个任务类中时,如果任务执行完没有对ThreadLocal进行remove操作,那么线程池中的线程ThreadLocalMap会一直保持这个已这个任务的ThreadLocal为key的value值。从而造成内存泄露。
ThreadLocal可能造成数据泄露
这个问题还不是太清楚。简单的理解就是,引用ThreadLocal的对象被垃圾回收了,即ThreadLocal被垃圾回收了,但是线程池中的线程没有回收的话,线程局部化的ThreadLocal.ThreadLocalMap不会被垃圾回收,造成了内存泄露。
更通俗的理解是:线程池中的ThreadLocal如果在线程任务执行完后不进行remove操作,那么ThreadLocal引用的对象将不会被垃圾回收,造成内存泄露。
网友评论