Thread Local Pattern Java

Intent

Securing variables global to a thread against being spoiled by other threads. That is needed if you use class variables or static variables in your Callable object or Runnable object that are not read-only.

By applying the Thread Local Pattern you can keep track of application instances or locale settings throughout the handling of a request. In Java, the ThreadLocal class works like a static variable, with the exception that it is only bound to the current thread! This allows us to use static variables in a thread-safe way.

Real-world example

In Java, thread-local variables are implemented by the ThreadLocal class object. This class provides thread-local variables.

ThreadLocal holds a variable of type T, which is accessible via get/set methods. For example, the ThreadLocal variable holding Integer value looks like this:
private static final ThreadLocal<Integer> myThreadLocalInteger = new ThreadLocal<Integer>();

Source code

In this example, the usage of SimpleDateFormat is made to be thread-safe. This is an example of the ThreadLocal pattern.

Class Diagram

Class diagram of Thread Local Pattern

Step 1: Create DateFormatCallable class which converts string dates to a date format using SimpleDateFormat. The date format and the date value will be passed to the Callable by the constructor. The constructor creates an instance of SimpleDateFormat and stores it in a ThreadLocal class variable.
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;
  }
}
Step 2: Create a Result object that will be returned by the Callable DateFormatCallable.
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;
  }
}
Step 3: Let's test this design pattern. Create ThreadLocalStorageDemo class converts the String date value 15/12/2015 to the Date format using the Java class SimpleDateFormat.
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;
  }
}

Applicability

Use the Thread Local Storage in any of the following situations
  • when you use class variables in your Callable / Runnable object that is not read-only and you use the same Callable instance in more than one thread running in parallel.
  • when you use static variables in your Callable / Runnable object that are not read-only and more than one instance of the Callable / Runnable may run in parallel threads.

Related patterns



Comments