天天看點

ThreadLocal小記

API:

public class ThreadLocal<T>      

extends

Object

該類提供了線程局部 (thread-local) 變量。

這些變量不同于它們的普通相應物。由于訪問某個變量(通過其 get 或 set 方法)的每一個線程都有自己的局部變量,它獨立于變量的初始化副本。ThreadLocal 執行個體一般是類中的 private static 字段,它們希望将狀态與某一個線程(比如,使用者 ID 或事務 ID)相關聯。

每一個線程都保持對其線程局部變量副本的隐式引用,僅僅要線程是活動的而且 ThreadLocal 執行個體是可訪問的。線上程消失之後。其線程局部執行個體的全部副本都會被垃圾回收(除非存在對這些副本的其它引用)。

Method:

initialValue

protected T initialValue()      

傳回此線程局部變量的目前線程的“初始值”。

線程第一次使用 ​

​get()​

​ 方法訪問變量時将調用此方法,但假設線程之前調用了 ​

​set(T)​

​ 方法。則不會對該線程再調用 initialValue 方法。

通常,此方法對每一個線程最多調用一次。但假設在調用​

​get()​

​ 後又調用了​

​remove()​

​,則可能再次調用此方法。

該實作傳回 null;假設程式猿希望線程局部變量具有 null 以外的值。則必須為 ThreadLocal 建立子類。并重寫此方法。通常将使用匿名内部類完畢此操作。

傳回:

傳回此線程局部變量的初始值

get

public T get()      

傳回此線程局部變量的目前線程副本中的值。假設變量沒實用于目前線程的值,則先将其初始化為調用 ​

​initialValue()​

​ 方法傳回的值。

此線程局部變量的目前線程的值

set

public void set(T value)      

将此線程局部變量的目前線程副本中的值設定為指定值。大部分子類不須要重寫此方法,它們僅僅依靠 ​

​initialValue()​

​ 方法來設定線程局部變量的值。

參數: ​

​value​

​ - 存儲在此線程局部變量的目前線程副本中的值。

remove

public void remove()      

移除此線程局部變量目前線程的值。

假設此線程局部變量随後被目前線程 讀取,且這期間目前線程沒有 設定其值。則将調用其 ​

​initialValue()​

​ 方法又一次初始化其值。

這将導緻在目前線程多次調用

initialValue 方法。

例1:

package com.example;

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class SafeTask implements Runnable{
  private static ThreadLocal<Date> startDate= new ThreadLocal<Date>() {
      protected Date initialValue(){
        return new Date();
      }
    };


  @Override
  public void run() {

      System.out.printf("Starting Thread: %s : %s\n",Thread.currentThread().getId(),startDate.get());
      try {
        TimeUnit.SECONDS.sleep( (int)Math.rint(Math.random()*10));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.printf("Thread Finished: %s : %s\n",Thread.currentThread().getId(),startDate.get());

  }

  public static void main(String[] args) {
    SafeTask task=new SafeTask();
      for (int i=0; i<3; i++){
        Thread thread=new Thread(task);
        thread.start();
        try {
          TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }

}      

執行結果:

Starting Thread: 15 : Mon Nov 17 13:36:33 CST 2014
Starting Thread: 17 : Mon Nov 17 13:36:35 CST 2014
Thread Finished: 17 : Mon Nov 17 13:36:35 CST 2014
Starting Thread: 18 : Mon Nov 17 13:36:37 CST 2014
Thread Finished: 15 : Mon Nov 17 13:36:33 CST 2014
Thread Finished: 18 : Mon Nov 17 13:36:37 CST 2014      

從結果,能夠看到,三個線程各自存儲和訪問各自維護的startDate局部變量。

解析:

ThreadLocal有一個内部靜态類ThreadLocalMap來管理線程中的變量。能夠簡單的将其了解成一個Map。

而Map中存放的指的方式是:用目前的線程來做為KEY。線程相應的變量值作為VALUE。

當某一線程要擷取目前變量的值時,就使用ThreadLocal.get()方法。通過線程自身作為KEY,去ThreadLocalMap中查找相應的值。

這樣就能夠解釋“每一個線程都保持對其線程局部變量副本的隐式引用”。

而為了使每一個線程都能夠使用該變量的副本使用,“ThreadLocal 執行個體一般是類中的 private static 字段”。

為了更好的了解ThreadLocal這樣的機制,請看以下的樣例。

例2:

package com.example;

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class SafeTask implements Runnable{

  private static ThreadLocal<Date> startDate = new ThreadLocal<Date>();


  @Override
  public void run() {
      startDate = new ThreadLocal<Date>();
      startDate.set(new Date());
      System.out.printf("Thread: %s new startDate.\n", Thread.currentThread().getId());
      System.out.printf("Starting Thread: %s : %s\n",Thread.currentThread().getId(),startDate.get());
      try {
        TimeUnit.SECONDS.sleep( (int)Math.rint(Math.random()*10));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.printf("Thread Finished: %s : %s\n",Thread.currentThread().getId(),startDate.get());

  }

  public static void main(String[] args) {
    SafeTask task=new SafeTask();
      for (int i=0; i<3; i++){
        Thread thread=new Thread(task);
        thread.start();
        try {
          TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }

}      

運作結果:

Thread: 15 new startDate.
Starting Thread: 15 : Mon Nov 17 13:55:16 CST 2014
Thread: 17 new startDate.
Starting Thread: 17 : Mon Nov 17 13:55:18 CST 2014
Thread Finished: 15 : null
Thread Finished: 17 : Mon Nov 17 13:55:18 CST 2014
Thread: 18 new startDate.
Starting Thread: 18 : Mon Nov 17 13:55:20 CST 2014
Thread Finished: 18 : Mon Nov 17 13:55:20 CST 2014      

為什麼例2的結果中會出現null呢?

原因就在于,代碼線上程中又一次建立來ThreadLocal

startDate = new ThreadLocal<Date>();      

這樣做,就導緻了startDate指向了新的ThreadLocal對象。那麼之前存放在當中的副本就丢失了,是以才會出現null的情況。

通過以上的樣例,我們就能了解為什麼ThreadLocal 執行個體一般是類中的 private static 字段。

我們須要明确。每一個線程中變量的副本,是通過線程的KEY和變量的VALUE存放在ThreadLocalMap中,而不是說。為每一個線程建立ThreadLocal。

就類而言,他實際僅僅維護和管理着一個ThreadLocal。