淺談ThreadLocal類
1- ThreadLocal類
ThreadLocal是在java.lang包下的類,可見這是一個非常重要的類!這個類有什麼作用呢?什麼情況下需要使用這個類呢?首先看看jdk中的關於這個類的註釋:以下截取了部分ThreadLocal類的註釋:
這個類(ThreadLocal)提供了線程局部變量。線程局部變量與普通變量的不同之處在於線程局部變量是每一個線程都維護了一份副本的,相互獨立並且可以通過get/set方法訪問!通常ThreadLocal在使用時被private static修飾,以便將ThreadLocal中值與線程關聯/綁定起來!
每一個線程在其生命週期內只要定義了線程局部變量並可以通過get/set方法訪問,就都持有一份線程局部變量副本的引用;等這個線程持有的局部變量就將被垃圾收集器回收,除非這些線程局部變量還可以通過其他途徑引用到!
由上可知,ThreadLocal可以爲使用相同變量的每個不同的線程都創建不同的存儲,這樣就可以多線程在共享資源的使用上產生衝突!
2- ThreadLocal常用方法
ThreadLocal常用的方法,比如initialValue,get,set!
2.1-initialValue方法:
返回當前線程的線程局部變量的初始值,此方法在第一次使用get方法獲取線程局部變量的時候被調用,如果在調用get方法之前使用了set方法設置了線程局部變量的值了,那麼initialValue方法將不會被調用!通常此方法對於每個線程來說只會調用一次,除非在調用remove方法之後又調用了get方法!此處定義返回null,需要程序員重寫這個方法定義在實際中初始的返回值!
2.2-get方法:
返回當前線程中持有的線程局部變量存儲的值,如果沒有這個值的話會先調用initialValue方法初始化一個線程局部變量並返回!
2.3-set方法
設置當前線程的線程局部變量的值,大多數情況下使用者不需要重寫這個方法,方法的入參value將被存儲在當前的線程的局部變量的副本中!
3- 示例代碼
下面通過一個例子來說明ThreadLocal方法的使用!本例中模擬JDBC中,通過將連接保存到ThreadLocal對象中,這樣每個線程都會擁有屬於自己的連接,代碼如下:
定義一個MyConnection類,表示連接:
package jdk.test.thread.local;
/**
* 連接
*/
public class MyConnection {
private String name;
public MyConnection() {
}
public MyConnection(String name) {
this.name = name;
}
/**
* 模擬執行SQL
*/
public void executeSQL(String sql) {
System.out.println("Connection:【" + name + "】,executeSQL:【" + sql + "】");
}
}
定義持有ThreadLocal的類:
package jdk.test.thread.local;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
/**
* ThreadLocal測試
*/
public class ThreadLocalVarHolder implements Runnable {
//假設這是個數據庫連接池
private static List<MyConnection> connectionList = new ArrayList<>();
//假設一共有5個數據庫連接
static {
connectionList = Lists.newArrayList(
new MyConnection("Con1"),
new MyConnection("Con2"),
new MyConnection("Con3"),
new MyConnection("Con4"),
new MyConnection("Con5"),
new MyConnection("Con6")
);
}
/**
* ThreadLocal:將線程中的某個值與保存這個值的對象關聯起來
*/
private static ThreadLocal<MyConnection> value = new ThreadLocal<MyConnection>() {
public MyConnection initialValue() {
//默認每次隨機取一個連接
if (connectionList.size() >= 1) {
return connectionList.remove((int) ((connectionList.size() - 1) * Math.random()));
} else {
throw new RuntimeException("沒有可用的連接了!");
}
}
};
/**
* 獲取連接的方法
*/
public static MyConnection getConnection() {
return value.get();
}
@Override
public void run() {
getConnection().executeSQL("select * from tableA;");
getConnection().executeSQL("update tableA set qty = 1 where id = 100;");
System.out.println("===================================");
connectionList.add(getConnection());
}
}
在本例中,模擬讓每個持有連接的線程執行兩條SQL,由於每個線程持有的線程局部變量MyConnection變量在此類初始化的時候就綁定了,run方法中三次調用getConnection方法返回的都是同一個MyConnection對象!
測試代碼如下:
package jdk.test.thread.local;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* ThreadLocal測試類
*/
public class ThreadLocalTest {
/**
* 測試ThreadLocal的類,通過線程池工具提交的方法執行
* @throws Exception
*/
@Test
public void test() throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executorService.execute(new ThreadLocalVarHolder());
}
TimeUnit.SECONDS.sleep(3);
executorService.shutdown();
}
}
運行測試方法,打印結果如下:
Connection:【Con5】,executeSQL:【select * from tableA;】
Connection:【Con5】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
Connection:【Con6】,executeSQL:【select * from tableA;】
Connection:【Con6】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
Connection:【Con4】,executeSQL:【select * from tableA;】
Connection:【Con4】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
Connection:【Con6】,executeSQL:【select * from tableA;】
Connection:【Con6】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
Connection:【Con4】,executeSQL:【select * from tableA;】
Connection:【Con4】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
每個Connection出現兩次!