什么时候应该使用ThreadLocal变量?
它是如何使用的?
什么时候应该使用ThreadLocal变量?
它是如何使用的?
当前回答
[参考]ThreadLocal无法解决共享对象的更新问题。建议使用staticThreadLocal对象,该对象由同一线程中的所有操作共享。 [必选]remove()方法必须由ThreadLocal变量实现,特别是在使用经常重用线程的线程池时。否则,可能会影响后续的业务逻辑,并导致意想不到的问题,如内存泄漏。
其他回答
在Java中,如果您有一个每个线程都可以变化的数据,那么您可以选择将该数据传递给每个需要(或可能需要)它的方法,或者将该数据与线程关联。如果你的所有方法都需要传递一个公共的“上下文”变量,那么到处传递数据可能是可行的。
如果不是这样,您可能不希望用额外的参数来打乱方法签名。在非线程环境中,可以使用Java中等价的全局变量来解决这个问题。在线程词中,与全局变量等价的是线程局部变量。
什么时候?
当一个对象不是线程安全的时候,为了避免影响可伸缩性的同步,给每个线程一个对象,并保持它的线程作用域,即ThreadLocal。最常用但不是线程安全的对象之一是数据库Connection和JMSConnection。
如何?
例如,Spring框架通过将这些连接对象保存在ThreadLocal变量中,在幕后大量使用ThreadLocal来管理事务。在高层,当事务启动时,它获得连接(并禁用自动提交)并将其保存在ThreadLocal中。在进一步的db调用中,它使用相同的连接与db通信。最后,它从ThreadLocal获取连接,提交(或回滚)事务并释放连接。
我认为log4j也使用ThreadLocal来维护MDC。
可以使用threadlocal变量的两个用例- 1-当我们需要将状态与线程关联时(例如,用户ID或事务ID)。这通常发生在web应用程序中,每个发送到servlet的请求都有一个与之关联的唯一transactionID。
// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
注意,这里的withInitial方法是使用lambda表达式实现的。 2-另一个用例是当我们想要一个线程安全的实例时,我们不想使用同步,因为同步的性能成本更高。其中一种情况是使用SimpleDateFormat。由于SimpleDateFormat不是线程安全的,所以我们必须提供机制使其线程安全。
public class ThreadLocalDemo1 implements Runnable {
// threadlocal variable is created
private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue(){
System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
return new SimpleDateFormat("dd/MM/yyyy");
}
};
public static void main(String[] args) {
ThreadLocalDemo1 td = new ThreadLocalDemo1();
// Two threads are created
Thread t1 = new Thread(td, "Thread-1");
Thread t2 = new Thread(td, "Thread-2");
t1.start();
t2.start();
}
@Override
public void run() {
System.out.println("Thread run execution started for " + Thread.currentThread().getName());
System.out.println("Date formatter pattern is " + dateFormat.get().toPattern());
System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
}
}
ThreadLocal是JVM专门提供的功能,仅为线程提供隔离的存储空间。与实例作用域变量的值一样,变量只能绑定到类的给定实例。每个对象都有其唯一的值,它们不能看到彼此的值。ThreadLocal变量的概念也是如此,在对象实例的意义上,它们是线程的本地线程,除了创建它的线程之外,其他线程看不到它。在这里看到的
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
public static void main(String[] args) {
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
}
}
第一个用例——每个线程上下文,提供线程安全性和性能 SpringFramework类中的实时示例-
LocaleContextHolder TransactionContextHolder RequestContextHolder DateTimeContextHolder
第二个用例——当我们不想在线程之间共享一些东西,同时由于性能成本而不想使用同步/锁定时 SimpleDateFormat为日期创建自定义格式
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author - GreenLearner(https://www.youtube.com/c/greenlearner)
*/
public class ThreadLocalDemo1 {
SimpleDateFormat sdf = new SimpleDateFormat("dd-mm-yyyy");//not thread safe
ThreadLocal<SimpleDateFormat> tdl1 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-dd-mm"));
public static void main(String[] args) {
ThreadLocalDemo1 d1 = new ThreadLocalDemo1();
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i=0; i<100; i++) {
es.submit(() -> System.out.println(d1.getDate(new Date())));
}
es.shutdown();
}
String getDate(Date date){
// String s = tsdf.get().format(date);
String s1 = tdl1.get().format(date);
return s1;
}
}
使用技巧
尽可能使用局部变量。这样我们就可以避免使用ThreadLocal 尽可能地将功能委托给框架 如果使用ThreadLocal并将状态设置在其中,请确保在使用后清理它,否则它可能成为OutOfMemoryError的主要原因