目的
将全局变量固定到线程,以防被其他线程破坏。如果在可调用对象或可运行对象中使用非只读的类变量或静态变量,则需要这样做。
通过应用 本地线程模式Thread Local Pattern, 您可以在处理请求的整个过程中跟踪应用程序实例或区域设置。在Java中,ThreadLocal类的工作方式类似于静态变量,但它只绑定到当前线程!这允许我们以线程安全的方式使用静态变量。
示例
在Java中,线程局部变量由ThreadLocal类对象实现。该类提供线程局部变量。ThreadLocal包含T类型的变量,可通过get / set方法访问。例如,持有Integer值的ThreadLocal变量如下所示:
<b>private</b> <b>static</b> <b>final</b> ThreadLocal<Integer> myThreadLocalInteger = <b>new</b> ThreadLocal<Integer>();
源代码
Java ThreadLocal类提供线程局部变量。这些变量不同于它们的正常对应变量,因为访问一个变量(通过get或set方法)的每个线程都有自己的独立初始化变量副本。ThreadLocal实例通常是希望将状态与线程(例如,用户ID或事务ID)相关联的类中的私有静态字段。通过应用ThreadLocal模式,您可以在处理请求期间跟踪应用程序实例或区域设置。ThreadLocal类的工作方式类似于静态变量,但它只绑定到当前线程!这允许我们以线程安全的方式使用静态变量。在Java中,线程局部变量由ThreadLocal类对象实现。ThreadLocal包含一个T类型的变量,可以通过get / set方法访问它。SimpleDateFormat是基本的Java类之一,不是线程安全的。如果不为每个线程隔离SimpleDateFormat实例,则会出现问题。
在本例中,simpledateformat的用法是线程安全的。这是一个 Thread Local Pattern 的示例。
类图
步骤1: 创建DateFormatCallable类,使用SimpleDateFormat将字符串日期转换为日期格式。日期格式和日期值将由构造函数传递给Callable。构造函数创建SimpleDateFormat的实例并将其存储在ThreadLocal类变量中。
<b>public</b> <b>class</b> DateFormatCallable implements Callable<Result> {
<font><i>// class variables (members)</i></font><font>
<b>private</b> ThreadLocal<DateFormat> df; </font><font><i>//TLTL </i></font><font>
</font><font><i>// private DateFormat df; //NTLNTL</i></font><font>
<b>private</b> String dateValue; </font><font><i>// for dateValue Thread Local not needed</i></font><font>
</font><font><i>/**
* 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"
*/</i></font><font>
<b>public</b> DateFormatCallable(String inDateFormat, String inDateValue) {
<b>final</b> String idf = inDateFormat; </font><font><i>//TLTL</i></font><font>
<b>this</b>.df = <b>new</b> ThreadLocal<DateFormat>() { </font><font><i>//TLTL</i></font><font>
@Override </font><font><i>//TLTL</i></font><font>
<b>protected</b> DateFormat initialValue() { </font><font><i>//TLTL</i></font><font>
<b>return</b> <b>new</b> SimpleDateFormat(idf); </font><font><i>//TLTL</i></font><font>
} </font><font><i>//TLTL</i></font><font>
}; </font><font><i>//TLTL</i></font><font>
</font><font><i>// this.df = new SimpleDateFormat(inDateFormat); //NTLNTL</i></font><font>
<b>this</b>.dateValue = inDateValue;
}
</font><font><i>/**
* @see java.util.concurrent.Callable#call()
*/</i></font><font>
@Override
<b>public</b> Result call() {
System.out.println(Thread.currentThread() + </font><font>" started executing..."</font><font>);
Result result = <b>new</b> Result();
</font><font><i>// Convert date value to date 5 times</i></font><font>
<b>for</b> (<b>int</b> i = 1; i <= 5; i++) {
<b>try</b> {
</font><font><i>// this is the statement where it is important to have the</i></font><font>
</font><font><i>// instance of SimpleDateFormat locally</i></font><font>
</font><font><i>// Create the date value and store it in dateList</i></font><font>
result.getDateList().add(<b>this</b>.df.get().parse(<b>this</b>.dateValue)); </font><font><i>//TLTL</i></font><font>
</font><font><i>// result.getDateList().add(this.df.parse(this.dateValue)); //NTLNTL</i></font><font>
} <b>catch</b> (Exception e) {
</font><font><i>// write the Exception to a list and continue work</i></font><font>
result.getExceptionList().add(e.getClass() + </font><font>": "</font><font> + e.getMessage());
}
}
System.out.println(Thread.currentThread() + </font><font>" finished processing part of the thread"</font><font>);
<b>return</b> result;
}
}
</font>
步骤2: 创建将由Callable DateFormatCallable返回的Result对象。
<b>public</b> <b>class</b> Result {
<font><i>// A list to collect the date values created in one thread</i></font><font>
<b>private</b> List<Date> dateList = <b>new</b> ArrayList<Date>();
</font><font><i>// A list to collect Exceptions thrown in one threads (should be none in</i></font><font>
</font><font><i>// this example)</i></font><font>
<b>private</b> List<String> exceptionList = <b>new</b> ArrayList<String>();
</font><font><i>/**
*
* @return List of date values collected within an thread execution
*/</i></font><font>
<b>public</b> List<Date> getDateList() {
<b>return</b> dateList;
}
</font><font><i>/**
*
* @return List of exceptions thrown within an thread execution
*/</i></font><font>
<b>public</b> List<String> getExceptionList() {
<b>return</b> exceptionList;
}
}
</font>
第3步: 测试。创建ThreadLocalStorageDemo类使用Java类SimpleDateFormat将String日期值15/12/2015转换为Date格式。它使用4个线程执行20次,每个线程执行5次。在DateFormatCallable中使用ThreadLocal一切都运行良好。但是如果你注释了ThreadLocal变量(标有“// TLTL”)并在非ThreadLocal变量中注释(用“// NTLNTL”标记),你可以看到没有ThreadLocal会发生什么。很可能您会得到错误的日期值及/或异常。
<b>public</b> <b>class</b> ThreadLocalStorageDemo {
<font><i>/**
* Program entry point
*
* @param args
* command line args
*/</i></font><font>
<b>public</b> <b>static</b> <b>void</b> main(String args) {
<b>int</b> counterDateValues = 0;
<b>int</b> counterExceptions = 0;
</font><font><i>// Create a callable</i></font><font>
DateFormatCallable callableDf = <b>new</b> DateFormatCallable(</font><font>"dd/MM/yyyy"</font><font>, </font><font>"15/12/2015"</font><font>);
</font><font><i>// start 4 threads, each using the same Callable instance</i></font><font>
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);
<b>try</b> {
Result result = <b>new</b> Result[4];
result[0] = futureResult1.get();
result[1] = futureResult2.get();
result[2] = futureResult3.get();
result[3] = futureResult4.get();
</font><font><i>// Print results of thread executions (converted dates and raised exceptions)</i></font><font>
</font><font><i>// and count them</i></font><font>
<b>for</b> (<b>int</b> i = 0; i < result.length; i++) {
counterDateValues = counterDateValues + printAndCountDates(result[i]);
counterExceptions = counterExceptions + printAndCountExceptions(result[i]);
}
</font><font><i>// a correct run should deliver 20 times 15.12.2015</i></font><font>
</font><font><i>// and a correct run shouldn't deliver any exception</i></font><font>
System.out.println(</font><font>"The List dateList contains "</font><font> + counterDateValues + </font><font>" date values"</font><font>);
System.out.println(</font><font>"The List exceptionList contains "</font><font> + counterExceptions + </font><font>" exceptions"</font><font>);
} <b>catch</b> (Exception e) {
System.out.println(</font><font>"Abnormal end of program. Program throws exception: "</font><font> + e);
}
executor.shutdown();
}
</font><font><i>/**
* Print result (date values) of a thread execution and count dates
*
* @param res contains results of a thread execution
*/</i></font><font>
<b>private</b> <b>static</b> <b>int</b> pr<b>int</b>AndCountDates(Result res) {
</font><font><i>// a correct run should deliver 5 times 15.12.2015 per each thread</i></font><font>
<b>int</b> counter = 0;
<b>for</b> (Date dt : res.getDateList()) {
counter++;
Calendar cal = Calendar.getInstance();
cal.setTime(dt);
</font><font><i>// Formatted output of the date value: DD.MM.YYYY</i></font><font>
System.out.println(
cal.get(Calendar.DAY_OF_MONTH) + </font><font>"."</font><font> + cal.get(Calendar.MONTH) + </font><font>"."</font><font> + +cal.get(Calendar.YEAR));
}
<b>return</b> counter;
}
</font><font><i>/**
* Print result (exceptions) of a thread execution and count exceptions
*
* @param res contains results of a thread execution
* @return number of dates
*/</i></font><font>
<b>private</b> <b>static</b> <b>int</b> pr<b>int</b>AndCountExceptions(Result res) {
</font><font><i>// a correct run shouldn't deliver any exception</i></font><font>
<b>int</b> counter = 0;
<b>for</b> (String ex : res.getExceptionList()) {
counter++;
System.out.println(ex);
}
<b>return</b> counter;
}
}[/i][/i]
</font>
适用性
在以下任何情况下使用线程本地存储