现在,并发应用程序最关键的方面之一是共享数据。当你创建线程实现Runnable接口,然后开始各种线程对象使用相同的Runnable对象,所有线程共享,Runnable对象内部定义相同的属性。这本质上意味着,如果您更改了线程中的任何属性,所有线程都将受到此更改的影响,并将通过第一个线程看到修改后的值。有时它是你希望的行为,例如多个线程增加/减少相同的计数器变量;但有时您希望确保每个线程都必须工作在自己的线程实例副本上,并且不影响其他数据。
ThreadLocal
每个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储来一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含独一无二的threadLocalHashCode值,使用这个值就可以在线程K-V值中终会对应的本地线程变量。
何时使用ThreadLocal
比如,你正在从事电子商务应用程序,需要为每个客户请求生成一个唯一的交易ID,控制器进程需要将此交易ID传递给Manager / DAO类中的业务方法,以便进行日志记录。一个解决方案可能是将此交易ID作为参数传递给所有业务方法。但这不是一个好的解决方案,因为代码是多余的和不必要的。
为了解决这个问题,在这里你可以使用ThreadLocal变量。你可以在控制器或任何预处理器拦截交易ID,并设置此交易ID到ThreadLocal里。在这之后,不管该控制器调用什么方法,它们都可以从ThreadLocal访问此交易ID。请注意,应用程序控制器将在一次服务多个请求,因为每个请求在单独的线程中在框架级别处理,交易ID将是每个线程唯一的,并且将从线程的执行路径中访问。
ThreadLocal API介绍
Java Concurrency API为ThreadLocal变量的使用提供良好的机制和优秀的性能。
public class ThreadLocal<T> extends Object {...}
该类提供线程本地变量。这些变量与一般的变量不同,每个线程访问一个线程(通过get或set方法)有自己独立的变量初始化副本。ThreadLocal实例通常是私有的静态字段在类希望关联状态的线程(例如,一个用户ID或交易ID)
这个类有以下方法:
get():返回该线程局部变量的当前线程的值复制。
initialvalue():返回该线程局部变量的当前线程的“初始值”。
remove():删除该线程局部变量的当前线程的值。
set(T value):将当前线程的本地线程变量的副本设置为指定的值。
如何使用ThreadLocal
下面的例子使用了两个线程局部变量,即threadId和startDate。它们都被定义为建议的“私有静态”字段。threadId将被用来确定当前正在运行的线程和startDate用来表示启动线程的执行的时间。以上信息将打印在控制台中,以验证每个线程是否保留了自己的变量副本。
public class DemoTask implements Runnable {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static int getThreadId() {
return threadId.get();
}
private static final ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
@Override
protected Date initialValue() {
return new Date();
}
};
@Override
public void run() {
System.out.printf("Starting Thread: %s : %s\n", getThreadId(), startDate.get());
try {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread Finished: %s : %s\n", getThreadId(), startDate.get());
}
public static void main(String[] args) {
DemoTask demoTask = new DemoTask();
Thread thread1 = new Thread(demoTask);
Thread thread2 = new Thread(demoTask);
Thread thread3 = new Thread(demoTask);
thread1.start();
thread2.start();
thread3.start();
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
现在来验证变量基本上能够保持自己的状态,不论多初始化为多少线程。我们创建了该任务的三个实例;启动线程;然后验证信息在控制台打印它们。
Starting Thread: 0 : Sat Aug 12 15:38:30 CST 2017
Starting Thread: 2 : Sat Aug 12 15:38:30 CST 2017
Starting Thread: 1 : Sat Aug 12 15:38:30 CST 2017
Thread Finished: 0 : Sat Aug 12 15:38:30 CST 2017
Thread Finished: 2 : Sat Aug 12 15:38:30 CST 2017
Thread Finished: 1 : Sat Aug 12 15:38:30 CST 2017
在上面的输出中,打印语句的顺序每次都会不同。我们可以清楚地看到ThreadLocal值为每个线程实例所保管。
最常见的ThreadLocal使用是当您有一些对象不是线程安全的,但您希望避免使用同步关键字/块同步访问该对象。相反,给每个线程自己的对象实例来工作。
一个很好的替代synchronization(同步)或ThreadLocal是使用局部变量。局部变量始终是线程安全的。唯一阻止你这样做的是应用程序设计约束。
在webapp服务器,它可能保持一个线程池,所以一个ThreadLocal变量应该在响应客户端请求前删除,因为当前线程可以被下一个请求重复使用。另外,如果你当你完成请求不清理的时候,任何引用它加载的类将保持在永久堆作为部署webapp的一部分,并永远不会被垃圾回收。
InheritableThreadLocal
InheritableThreadLocal类是ThreadLocal的子类。为了解决ThreadLocal实例内部每个线程都只能看到自己的私有值,所以InheritableThreadLocal允许一个线程创建的所有子线程访问其父线程的值。
参考
1.Java ThreadLocal Variables – When and How to Use?
2.Java TheadLocal
网友评论