五、你有我有全都有—— ThreadLocal如何解決並發安全性?
前面我們介紹了Java當中多個線程搶佔一個共用資源的問題。但不論是同步還是重入鎖,都不能實實在在的解決資源緊缺的情況,這些 方案只是靠制定規則來約束線程的行為,讓它們不再拚命的爭搶,而不是真正從實質上解決他們對資源的需求。
在JDK 1.2當中,引入了java.lang.ThreadLocal。它為我們提供了一種全新的思路來解決線程並發的問題。但是他的名字難免讓我們望 文生義:本地線程?
什麼是本地線程?
本地線程開玩笑的說:不要迷戀哥,哥只是個傳說。
其實ThreadLocal並非Thread at Local,而是LocalVariable in a Thread。
根據WikiPedia上的介紹,ThreadLocal其實是源於一項多線程技術,叫做Thread Local Storage,即執行緒區域儲存技術。不僅僅是Java ,在C++、C#、.NET、Python、Ruby、Perl等開發平台上,該技術都已經得以實現。
當使用ThreadLocal維護變數時,它會為每個使用該變數的線程提供獨立的變數副本。也就是說,他從根本上解決的是資源數量的問題 ,從而使得每個線程持有相對獨立的資源。這樣,當多個線程進行工作的時候,它們不需要糾結於同步的問題,於是效能便大大提升。但 資源的擴張帶來的是更多的空間消耗,ThreadLocal就是這樣一種利用空間來換取時間的解決方案。
說了這麼多,來看看如何正確使用ThreadLocal。
通過研究JDK文檔,我們知道,ThreadLocal中有幾個重要的方法:get()、set()、remove()、initailValue(),對應的含義分別是:
返回此線程局部變數的當前線程副本中的值、將此線程局部變數的當前線程副本中的值設定為指定值、移除此線程局部變數當前線程的 值、返回此線程局部變數的當前線程的“初始值”。
還記得我們在第三篇的上半節引出的那個例子嗎?幾個線程修改同一個Student對象中的age屬性。為了保證這幾個線程能夠工作正常, 我們需要對Student的對象進行同步。
下面我們對這個程式進行一點小小的改造,我們通過繼承Thread來實現多線程:
/**
*
* @author x-spirit
*/
public class ThreadDemo3 extends Thread{
private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>();
public ThreadDemo3(Student stu){
stuLocal.set(stu);
}
public static void main(String[] args) {
Student stu = new Student();
ThreadDemo3 td31 = new ThreadDemo3(stu);
ThreadDemo3 td32 = new ThreadDemo3(stu);
ThreadDemo3 td33 = new ThreadDemo3(stu);
td31.start();
td32.start();
td33.start();
}
@Override
public void run() {
accessStudent();
}
public void accessStudent() {
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running!");
Random random = new Random();
int age = random.nextInt(100);
System.out.println("thread " + currentThreadName + " set age to:" + age);
Student student = stuLocal.get();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge ());
}
}