Java | 多线程 | ThreadLocal结合线程池的正确使用方式

1)问题:

才发现,写这篇博客之前,自己一直在以一种错误的姿势在用threadLocal对象。
场景就是threadLocal在项目中使用时,出现取值错误的情况。花了不少时间排查,最终还是排查到线程池上。之前一直没有问题,或许是因为并发不高。最终今天还是遇到了问题(出来混,迟早是要还的)。
不禁开始怀疑:threadLocal遇到线程池就不好用了?

2)分析:

我们都知道threadLocal中维护了一个线程和value的映射,当前线程的threadLocal即为key,value为引用的对象。

t.threadLocals = new ThreadLocalMap(this, firstValue);

每个线程保存一份,达到线程安全。
但是在线程复用的情况下,threadLocal并不能保证按照预期执行,很有可能出现数据错乱。原因就是线程池中的线程在还未销毁的情况下,新的请求进来,会继续复用线程池中的线程,而这些线程在之前处理的过程中,对应的threadLocal有可能已经有值,导致出错。代码如下:

/**
 * Created by zhangshukang on 2018/7/27.
 */
public class ThreadLocalTest {

    public static void main(String[] args) throws InterruptedException {

        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

        ExecutorService pool = new ThreadPoolExecutor(
                1,
                1,
                50000L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(1024),
                new ThreadFactory() {
                    private AtomicInteger number = new AtomicInteger(0);
                    @Override
                    public Thread newThread(Runnable runnable) {
                        return new Thread(runnable, "admin" + "-" + number.getAndIncrement());
                    }
                },
                new ThreadPoolExecutor.AbortPolicy());



        pool.execute(()->{
            System.out.println(Thread.currentThread().getName());
            threadLocal.set(5);
            System.out.println(threadLocal.get());
        });


        Thread.sleep(3000l);
        System.out.println("-------------------");


        pool.execute(()->{
            System.out.println(Thread.currentThread().getName());
            System.out.println(threadLocal.get());
        });

    }
}

执行结果如下:

admin-0
5
-------------------
admin-0
5

可以看到上面的执行结果:线程 admin-0 复用,输出了相同的变量。

3)正确姿势:

正确姿势是在threadLocal变量使用之后,调用remove()方法。这么做也可以避免内存泄露,如果没有调用该方法,那当map中当前线程被回收,对应的value得不到回收,容易引起内存泄露。

这样又引出了新问题,就是我们怎么区分在什么地方进行remove()呢?假如一个接口调用了remove(),然后结束了代码逻辑,这没问题。但是有可能另一个接口也执行完该代码块remove(),接着又在别的地方调用了get()方法,这种场景还是会出错。
所以尽量避免这种使用场景,要保证remove()的代码块的调用链后面,不会再执行get方法。

猜你喜欢

转载自blog.csdn.net/woshilijiuyi/article/details/81240663