Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/365.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在CompletableFuture中使用ThreadLocal安全吗?_Java_Asynchronous - Fatal编程技术网

Java 在CompletableFuture中使用ThreadLocal安全吗?

Java 在CompletableFuture中使用ThreadLocal安全吗?,java,asynchronous,Java,Asynchronous,ThreadLocal将数据绑定到特定线程。对于CompletableFuture,它使用线程池中的线程执行,该线程可能是不同的线程 这是否意味着在执行CompletableFuture时,它可能无法从ThreadLocal获取数据?是的,scala Futures具有Local.scala,它通过thenCompose/thenApply方法(即flatMap/map方法)传输状态。Java确实缺少了这一点,这使得创建平台的平台开发人员很难将状态从平台传递给客户端代码,然后客户端代码再调用回平

ThreadLocal将数据绑定到特定线程。对于CompletableFuture,它使用线程池中的线程执行,该线程可能是不同的线程


这是否意味着在执行CompletableFuture时,它可能无法从ThreadLocal获取数据?

是的,scala Futures具有Local.scala,它通过thenCompose/thenApply方法(即flatMap/map方法)传输状态。Java确实缺少了这一点,这使得创建平台的平台开发人员很难将状态从平台传递给客户端代码,然后客户端代码再调用回平台。这很令人沮丧,但scala有太多的功能(就像厨房水槽),所以虽然简洁,但我发现项目失控的速度更快,因为人类(包括我)都倾向于做出不同的选择

访问ThreadLocal(通过其get或set方法)的每个线程都有 它自己的、独立初始化的变量副本

因此,当使用
ThreadLocal.get
时,不同的线程将收到不同的值;当使用
ThreadLocal.set
时,不同的线程也会设置自己的值;不同线程之间不会有ThreadLocal的内部/存储/自身值重叠

但是,因为问题是关于线程池组合的安全性,我将指出特定于该特殊组合的特定风险:

对于足够数量的调用,池中的线程有可能被重用(这就是池的全部要点:)。假设我们有pool-thread1,它执行task1,现在正在执行task2;如果task1在完成其作业之前未将其从
ThreadLocal
中删除,则task2将重用与task1相同的ThreadLocal值!重用可能不是您想要的

检查以下测试;他们最好能证明我的观点

包ro.go.adrhc.concurrent;
导入lombok.extern.slf4j.slf4j;
导入org.junit.jupiter.api.Test;
导入java.util.concurrent.ExecutionException;
导入java.util.concurrent.ExecutorService;
导入java.util.concurrent.Executors;
导入java.util.concurrent.ThreadLocalRandom;
导入静态org.junit.Assert.assertEquals;
导入静态org.junit.jupiter.api.Assertions.assertNotEquals;
@Slf4j
类ThreadLocalTest{
/**
*拥有1个线程,以便有100%的机会使用ThreadLocal变量的相同副本执行2个任务。
*/
private Executors Service es=Executors.newSingleThreadExecutor();
私有ThreadLocal缓存=新ThreadLocal();
/**
*随机初始化不是正确清理的替代方案!
*/
private ThreadLocal cacheWithInitVal=ThreadLocal.withInitial(
ThreadLocalRandom.current()::nextDouble);
@试验
void reuseThreadWithCleanup()引发ExecutionException、InterruptedException{
var future1=es.submit(()->this.doSomethingWithCleanup(缓存));
var future2=es.submit(()->this.doSomethingWithCleanup(缓存));
assertNotEquals(future1.get(),future2.get());//不同的runnable只是使用了不同的ThreadLocal值
}
@试验
void reusethreadwithoutitval()引发ExecutionException、InterruptedException{
var future1=es.submit(()->this.doSomething(缓存));
var future2=es.submit(()->this.doSomething(缓存));
assertEquals(future1.get(),future2.get());//不同的runnable只使用了相同的ThreadLocal值
}
@试验
void reuseThreadWithInitVal()引发ExecutionException、InterruptedException{
var future1=es.submit(()->this.doSomething(cacheWithInitVal));
var future2=es.submit(()->this.doSomething(cacheWithInitVal));
assertEquals(future1.get(),future2.get());//不同的runnable只使用了相同的ThreadLocal值
}
专用双剂量缓存(线程本地缓存){
if(cache.get()==null){
//在不为null时重用ThreadLocal的值
set(ThreadLocalRandom.current().nextDouble());
}
debug(“thread:{},cache:{}”,thread.currentThread().toString(),cache.get());
返回cache.get();
}
专用双Double doSomethingWithCleanup(线程本地缓存){
试一试{
返回doSomething(缓存);
}最后{
cache.remove();
}
}
}

一般来说,答案是否定的,在将来使用ThreadLocal是不安全的。主要原因是ThreadLocal变量是线程有界的。这些变量的目标是在当前线程中使用。然而,CompletableFuture的后端是线程池,这意味着线程由多个任务以随机顺序共享。使用ThreadLocal将产生两种后果:

  • 您的任务无法获取ThreadLocal变量,因为线程池中的线程不知道原始线程的ThreadLocal变量
  • 如果强制将线程局部变量放入由CompletableFuture执行的任务中,则会“污染”其他任务的线程局部变量。例如,如果您将用户信息存储在ThreadLocal中,一个用户可能会意外获得另一个用户的信息

  • 因此,在CompletableFuture中需要非常小心地避免使用ThreadLocal。

    是的,没错。除非您准备了自己的线程池,其中每个线程都带有所需的ThreadLocal变量。@AlexeiKaigorodov如果我使用thenApply而不是thenApplyAsync,这是否保证我可以始终在同一个线程中执行?所以我可以使用ThreadLocal来存储/读取数据。不保证在哪个线程上执行thenApply。如果您想访问一些公共数据,请将该数据放在类变量中,并使用该类的方法作为参数,然后应用()。