Thread Local 模式介绍

ThreadLocal详解

使用的目的

        保护全局变量到一个线程,防止被其他线程破坏。如果你在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;
  }
}

适用性

在以下任何情况下使用Thread Local 
  • 当你在Callable / Runnable对象中使用不是只读的类变量,并且在多个并行运行的线程中使用相同的Callable实例时。
  • 当你在Callable / Runnable对象中使用不是只读的静态变量时,Callable / Runnable的多个实例可能会在并行线程中运行。

本文翻译自谷歌Java社群,由于本人水平有限,可能翻译的不够准确,如有错误欢迎指正!

原文链接:https://plus.google.com/u/0/103003859659574905974/posts/68GmtRDZBtg



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值