使用的目的
保护全局变量到一个线程,防止被其他线程破坏。如果你在Callable对象或Runnable对象中使用不是只读的类变量或静态变量时,必需使用ThreadLocal。
通过应用Thread Local,你可以在整个处理请求过程中跟踪应用程序实例或本地设置。在Java中,ThreadLocal类像静态变量一样工作,区别在于它只能绑定到当前线程!使得我们可以8以线程安全的方式使用静态变量。
现实世界的例子
在Java中,线程局部变量由ThreadLocal类对象实现。该类提供线程局部变量。ThreadLocal保存T类型的变量,可通过get / set方法访问。例如,保存Integer值的ThreadLocal变量如下所示:
private static final ThreadLocal<Integer> myThreadLocalInteger = new ThreadLocal<Integer>();
源代码
Java种ThreadLocal类提供线程局部变量。这些变量与它们副本的不同之处在于,访问一个变量的每个线程(通过其get或set方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是一个私有静态的作为类中与线程相关联的字段。(如user ID oh 或者Transaction ID)
通过应用ThreadLocal模式,你可以在整个处理请求过程中跟踪应用程序实例或字段设置。除了它只能绑定到当前线程,ThreadLocal类可以像静态变量一样工作,这样就允许我们以线程安全的方式使用静态变量。在Java中,线程局部变量由ThreadLocal类对象实现。ThreadLocal包含一个T类型的变量,可以通过get / set方法访问它。例如:SimpleDateFormat是基本的Java类之一,并且不是线程安全的。如果你没有为每个线程分离SimpleDateFormat的实例,就会出现问题。
在下面这个例子中,SimpleDateFormat的使用被设置为线程安全的。这是ThreadLocal模式的一个很明显的例子。
类图
第1步:创建DateFormatCallable类,它使用SimpleDateFormat将字符串日期转换为日期格式。日期格式和日期值将由构造函数传递给Callable。构造函数创建一个SimpleDateFormat实例并将其存储在一个ThreadLocal类变量中。
public class DateFormatCallable implements Callable<Result> {
// class variables (members)
private ThreadLocal<DateFormat> df; //TLTL
// private DateFormat df; //NTLNTL
private String dateValue; // for dateValue Thread Local not needed
/**
* The date format and the date value are passed to the constructor
*
* @param inDateFormat
* string date format string, e.g. "dd/MM/yyyy"
* @param inDateValue
* string date value, e.g. "21/06/2016"
*/
public DateFormatCallable(String inDateFormat, String inDateValue) {
final String idf = inDateFormat; //TLTL
this.df = new ThreadLocal<DateFormat>() { //TLTL
@Override //TLTL
protected DateFormat initialValue() { //TLTL
return new SimpleDateFormat(idf); //TLTL
} //TLTL
}; //TLTL
// this.df = new SimpleDateFormat(inDateFormat); //NTLNTL
this.dateValue = inDateValue;
}
/**
* @see java.util.concurrent.Callable#call()
*/
@Override
public Result call() {
System.out.println(Thread.currentThread() + " started executing...");
Result result = new Result();
// Convert date value to date 5 times
for (int i = 1; i <= 5; i++) {
try {
// this is the statement where it is important to have the
// instance of SimpleDateFormat locally
// Create the date value and store it in dateList
result.getDateList().add(this.df.get().parse(this.dateValue)); //TLTL
// result.getDateList().add(this.df.parse(this.dateValue)); //NTLNTL
} catch (Exception e) {
// write the Exception to a list and continue work
result.getExceptionList().add(e.getClass() + ": " + e.getMessage());
}
}
System.out.println(Thread.currentThread() + " finished processing part of the thread");
return result;
}
}
第2步:创建将由Callable DateFormatCallable返回的Result对象。
public class Result {
// A list to collect the date values created in one thread
private List<Date> dateList = new ArrayList<Date>();
// A list to collect Exceptions thrown in one threads (should be none in
// this example)
private List<String> exceptionList = new ArrayList<String>();
/**
*
* @return List of date values collected within an thread execution
*/
public List<Date> getDateList() {
return dateList;
}
/**
*
* @return List of exceptions thrown within an thread execution
*/
public List<String> getExceptionList() {
return exceptionList;
}
}
第3步:让我们测试这种设计模式。创建ThreadLocalStorageDemo类使用Java类SimpleDateFormat将字符串日期值15/12/2015转换为日期格式。它使用4个线程执行此操作20次,每次执行5次。通过在DateFormatCallable中使用ThreadLocal,一切运行良好。但是,如果你注释掉ThreadLocal变体(ThreadLocal variant,标记为“// TLTL”)并注释非ThreadLocal变体(标记为“// NTLNTL”),则可以看到没有ThreadLocal会发生什么。很可能你会得到不正确的日期值和/或例外。public class ThreadLocalStorageDemo {
/**
* Program entry point
*
* @param args
* command line args
*/
public static void main(String[] args) {
int counterDateValues = 0;
int counterExceptions = 0;
// Create a callable
DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015");
// start 4 threads, each using the same Callable instance
ExecutorService executor = Executors.newCachedThreadPool();
Future<Result> futureResult1 = executor.submit(callableDf);
Future<Result> futureResult2 = executor.submit(callableDf);
Future<Result> futureResult3 = executor.submit(callableDf);
Future<Result> futureResult4 = executor.submit(callableDf);
try {
Result[] result = new Result[4];
result[0] = futureResult1.get();
result[1] = futureResult2.get();
result[2] = futureResult3.get();
result[3] = futureResult4.get();
// Print results of thread executions (converted dates and raised exceptions)
// and count them
for (int i = 0; i < result.length; i++) {
counterDateValues = counterDateValues + printAndCountDates(result[i]);
counterExceptions = counterExceptions + printAndCountExceptions(result[i]);
}
// a correct run should deliver 20 times 15.12.2015
// and a correct run shouldn't deliver any exception
System.out.println("The List dateList contains " + counterDateValues + " date values");
System.out.println("The List exceptionList contains " + counterExceptions + " exceptions");
} catch (Exception e) {
System.out.println("Abnormal end of program. Program throws exception: " + e);
}
executor.shutdown();
}
/**
* Print result (date values) of a thread execution and count dates
*
* @param res contains results of a thread execution
*/
private static int printAndCountDates(Result res) {
// a correct run should deliver 5 times 15.12.2015 per each thread
int counter = 0;
for (Date dt : res.getDateList()) {
counter++;
Calendar cal = Calendar.getInstance();
cal.setTime(dt);
// Formatted output of the date value: DD.MM.YYYY
System.out.println(
cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + +cal.get(Calendar.YEAR));
}
return counter;
}
/**
* Print result (exceptions) of a thread execution and count exceptions
*
* @param res contains results of a thread execution
* @return number of dates
*/
private static int printAndCountExceptions(Result res) {
// a correct run shouldn't deliver any exception
int counter = 0;
for (String ex : res.getExceptionList()) {
counter++;
System.out.println(ex);
}
return counter;
}
}
适用性
- 当你在Callable / Runnable对象中使用不是只读的类变量,并且在多个并行运行的线程中使用相同的Callable实例时。
- 当你在Callable / Runnable对象中使用不是只读的静态变量时,Callable / Runnable的多个实例可能会在并行线程中运行。
本文翻译自谷歌Java社群,由于本人水平有限,可能翻译的不够准确,如有错误欢迎指正!
原文链接:https://plus.google.com/u/0/103003859659574905974/posts/68GmtRDZBtg