目的
将全局变量固定到线程,以防被其他线程破坏。如果在可调用对象或可运行对象中使用非只读的类变量或静态变量,则需要这样做。
通过应用 本地线程模式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>
适用性
在以下任何情况下使用线程本地存储