转载

ThreadLocal模式

目的

将全局变量固定到线程,以防被其他线程破坏。如果在可调用对象或可运行对象中使用非只读的类变量或静态变量,则需要这样做。

通过应用 本地线程模式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 的示例。

类图

ThreadLocal模式

步骤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>

适用性

在以下任何情况下使用线程本地存储

  • 当您在Callable / Runnable对象中使用非只读的类变量时,在并行运行的多个线程中使用相同的Callable实例。
  • 当您在Callable / Runnable对象中使用非只读的静态变量时,Callable / Runnable的多个实例可以在并行线程中运行。
原文  https://www.jdon.com/52113
正文到此结束
Loading...