作者:indexchen
http://blog.csdn.net/indexchen/archive/2007/07/11/1685226.aspx
在Java的多線程編程中,為保證多個線程對共享變量的安全訪問,通常會使用synchronized來保證同一時刻只有一個線程對共享變量進(jìn)行操作。但在有些情況下,synchronized不能保證多線程對共享變量的正確讀寫。例如類有一個類變量,該類變量會被多個類方法讀寫,當(dāng)多線程操作該類的實例對象時,如果線程對類變量有讀取、寫入操作就會發(fā)生類變量讀寫錯誤,即便是在類方法前加上synchronized也無效,因為同一個線程在兩次調(diào)用方法之間時鎖是被釋放的,這時其它線程可以訪問對象的類方法,讀取或修改類變量。這種情況下可以將類變量放到ThreadLocal類型的對象中,使變量在每個線程中都有獨(dú)立拷貝,不會出現(xiàn)一個線程讀取變量時而被另一個線程修改的現(xiàn)象。
下面舉例說明:
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中分別設(shè)置了類變量sql和ThreadLocal變量,使用時先創(chuàng)建 QuerySvc的一個實例對象,然后產(chǎn)生多個線程,分別設(shè)置不同的sql實例對象,然后再調(diào)用execute方法,讀取sql的值,看是否是set方法中寫入的值。這種場景類似web應(yīng)用中多個請求線程攜帶不同查詢條件對一個servlet實例的訪問,然后servlet調(diào)用業(yè)務(wù)對象,并傳入不同查詢條件,最后要保證每個請求得到的結(jié)果是對應(yīng)的查詢條件的結(jié)果。
使用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();
}
}
運(yùn)行線程代碼如下:
QuerySvc qs = new QuerySvc();
for (int k=0; k<10>
String sql = "Select * from table where id =" + k;
new Work(qs,sql).start();
}
先創(chuàng)建一個QuerySvc實例對象,然后創(chuàng)建若干線程來調(diào)用QuerySvc的set和execute方法,每個線程傳入的sql都不一樣,從運(yùn)行結(jié)果可以看出sql變量中值不能保證在execute中值和set設(shè)置的值一樣,在 web應(yīng)用中就表現(xiàn)為一個用戶查詢的結(jié)果不是自己的查詢條件返回的結(jié)果,而是另一個用戶查詢條件的結(jié)果;而ThreadLocal中的值總是和set中設(shè)置的值一樣,這樣通過使用ThreadLocal獲得了線程安全性。
如果一個對象要被多個線程訪問,而該對象存在類變量被不同類方法讀寫,為獲得線程安全,可以用ThreadLocal來替代類變量。