作者:indexchen

http://blog.csdn.net/indexchen/archive/2007/07/11/1685226.aspx

在Java的多線程編程中,為保證多個線程對共享變量的安全訪問,通常會使用synchronized來保證同一時刻只有一個線程對共享變量進行操作。但在有些情況下,synchronized不能保證多線程對共享變量的正確讀寫。例如類有一個類變量,該類變量會被多個類方法讀寫,當多線程操作該類的實例對象時,如果線程對類變量有讀取、寫入操作就會發生類變量讀寫錯誤,即便是在類方法前加上synchronized也無效,因為同一個線程在兩次調用方法之間時鎖是被釋放的,這時其它線程可以訪問對象的類方法,讀取或修改類變量。這種情況下可以將類變量放到ThreadLocal類型的對象中,使變量在每個線程中都有獨立拷貝,不會出現一個線程讀取變量時而被另一個線程修改的現象。

下面舉例說明:

public class QuerySvc {
 private String sql;
 private  static ThreadLocal sqlHolder = new ThreadLocal();
 public QuerySvc() {
 }

 public  void execute() {
  System.out.println("Thread " + Thread.currentThread().getId() +" Sql  is " + sql);
  System.out.println("Thread " + Thread.currentThread().getId() +" Thread Local variable Sql is " + sqlHolder.get());
 }

 public  String getSql() {
  return sql;
 }

 public  void setSql(String sql) {
  this.sql = sql;
  sqlHolder.set(sql);
 }
}

為了說明多線程訪問對于類變量和ThreadLocal變量的影響,QuerySvc中分別設置了類變量sql和ThreadLocal變量,使用時先創建 QuerySvc的一個實例對象,然后產生多個線程,分別設置不同的sql實例對象,然后再調用execute方法,讀取sql的值,看是否是set方法中寫入的值。這種場景類似web應用中多個請求線程攜帶不同查詢條件對一個servlet實例的訪問,然后servlet調用業務對象,并傳入不同查詢條件,最后要保證每個請求得到的結果是對應的查詢條件的結果。

使用QuerySvc的工作線程如下:

public class Work extends Thread {
 private QuerySvc querySvc;
 private String sql;
 public Work(QuerySvc querySvc,String sql) {
  this.querySvc = querySvc;
  this.sql = sql;
 }
 public void run() {
  querySvc.setSql(sql);
  querySvc.execute();
 }
}

運行線程代碼如下:

QuerySvc qs = new QuerySvc();
   for (int k=0; k<10>
    String sql = "Select * from table where id =" + k;
    new Work(qs,sql).start();
}

先創建一個QuerySvc實例對象,然后創建若干線程來調用QuerySvc的set和execute方法,每個線程傳入的sql都不一樣,從運行結果可以看出sql變量中值不能保證在execute中值和set設置的值一樣,在 web應用中就表現為一個用戶查詢的結果不是自己的查詢條件返回的結果,而是另一個用戶查詢條件的結果;而ThreadLocal中的值總是和set中設置的值一樣,這樣通過使用ThreadLocal獲得了線程安全性。

如果一個對象要被多個線程訪問,而該對象存在類變量被不同類方法讀寫,為獲得線程安全,可以用ThreadLocal來替代類變量。