1.問題引入
多個業務模塊針對同一個static變量的操作 要保證在不同線程中 各模塊操作的是自身對應的變量對象
例如:
package org.lkl.thead; import java.util.Random; /** * 線程共享數據 * Function : * @author : Liaokailin * CreateDate : 2014-6-12 * version : 1.0 */ public class ThreadShareData { private static int data = 0 ; public static void main(String[] args) { for(int i = 0 ;i<2 ;i++){ new Thread(new Runnable(){ @Override public void run() { data = new Random().nextInt(); System.out.println(Thread.currentThread().getName()+ " put random data:"+data); new A().get() ; new B().get() ; } }).start() ; } } static class A { public int get(){ System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); return data ; } } static class B{ public int get(){ System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data); return data ; } } }
模塊A ,B都需要訪問static的變量data 在線程0中會隨機生成一個data值 假設為10 那么此時模塊A和模塊B在線程0中得到的data的值為10 ;在線程1中 假設會為data賦值為20 那么在當前線程下
模塊A和模塊B得到data的值應該為20
看程序執行的結果:
Thread-0 put random data:-1344602819 Thread-1 put random data:-1842611697 A from Thread-1 get data :-1842611697 A from Thread-0 get data :-1842611697 B from Thread-1 get data :-1842611697 B from Thread-0 get data :-1842611697
在線程0中執行模塊A和模塊B的方法去獲取data的值 但是在獲取之前 線程1就將data的值給修改為-1842611697 導致線程0中的模塊A和模塊B獲取了錯誤的數據.
2.解決問題
那么如何得到正確的效果呢?
可是將data數據和當前允許的線程綁定在一塊,在模塊A和模塊B去獲取數據data的時候 是通過當前所屬的線程去取得data的結果就行了。
聲明一個Map集合 集合的Key為Thread 存儲當前所屬線程 Value 保存data的值,代碼如下:
package org.lkl.thead.sharedata; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * 線程共享數據 * Function : * @author : Liaokailin * CreateDate : 2014-6-12 * version : 1.0 */ public class ThreadShareData { private static int data = 0 ; private static Map<Thread,Integer> threadData = new HashMap<Thread, Integer>() ; public static void main(String[] args) { for(int i = 0 ;i<2 ;i++){ new Thread(new Runnable(){ @Override public void run() { synchronized (ThreadShareData.class) { //保證下面的代碼都執行完才允許其他線程進入 data = new Random().nextInt(); threadData.put(Thread.currentThread(), data) ; //將數據綁定到帶當前線程 System.out.println(Thread.currentThread().getName()+ " put random data:"+data); new A().get() ; new B().get() ; } } }).start() ; } } static class A { public int get(){ //取數據都從當前線程中取得 int d = threadData.get(Thread.currentThread()) ; System.out.println("A from " + Thread.currentThread().getName() + " get data :" + d); return data ; } } static class B{ public int get(){ //取數據都從當前線程中取得 int d = threadData.get(Thread.currentThread()) ; System.out.println("B from " + Thread.currentThread().getName() + " get data :" + d); return data ; } } }
程序執行的結果:
Thread-0 put random data:-1842492021 A from Thread-0 get data :-1842492021 B from Thread-0 get data :-1842492021 Thread-1 put random data:-1886142929 A from Thread-1 get data :-1886142929 B from Thread-1 get data :-1886142929
3 問題的拓展
將數據與當前線程相掛鈎的話 那么顯然是可以利用ThreadLocal這個類 那么改造上面的代碼得到下面的代碼:
package org.lkl.thead.sharedata; import java.util.Random; /** * 線程共享數據 * Function : * @author : Liaokailin * CreateDate : 2014-6-12 * version : 1.0 */ public class ThreadShareData { private static int data = 0 ; /** * ThreadLocal 類似於map集合 只是集合的key是當前正在運行的線程 * 通過ThreadLocal可以將變量(或者是變量的容器)與當前線程相綁定. */ private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() ; public static void main(String[] args) { for(int i = 0 ;i<2 ;i++){ new Thread(new Runnable(){ @Override public void run() { synchronized (ThreadShareData.class) { //保證下面的代碼都執行完才允許其他線程進入 data = new Random().nextInt(); threadLocal.set(data) ; //將數據綁定到帶當前線程 System.out.println(Thread.currentThread().getName()+ " put random data:"+data); new A().get() ; new B().get() ; } } }).start() ; } } static class A { public int get(){ //取數據都從當前線程中取得 int d = threadLocal.get() ; System.out.println("A from " + Thread.currentThread().getName() + " get data :" + d); return data ; } } static class B{ public int get(){ //取數據都從當前線程中取得 int d = threadLocal.get() ; System.out.println("B from " + Thread.currentThread().getName() + " get data :" + d); return data ; } } }
代碼執行完以后會得到同樣的效果。
接下來還有一個問題 上面一直討論的是線程間共享一個變量 那么如果是多個變量呢?
要直接ThreadLocal每次只能是一個變量和當前線程掛鈎 如果有多個變量要和當前線程掛鈎的話 就得生命多個ThreadLocal對象 這樣子做很顯然是不合理的。那如何處理呢?
由於ThreadLocal可以並且只能和一個變量掛鈎,那么我們可以將這個變量設置為一個變量的容器,容器中可以存在多個變量,這也就是將需要和當前線程綁定的變量封裝到一個實體類中
package org.lkl.thead.sharedata; import java.util.Random; /** * 線程共享數據 * Function : * @author : Liaokailin * CreateDate : 2014-6-12 * version : 1.0 */ public class ThreadShareData { private static int data = 0 ; public static void main(String[] args) { for(int i = 0 ;i<2 ;i++){ new Thread(new Runnable(){ @Override public void run() { synchronized (ThreadShareData.class) { //保證下面的代碼都執行完才允許其他線程進入 data = new Random().nextInt(); VariableContainer.getThreadInstance().setAge(data) ; VariableContainer.getThreadInstance().setName("liaokailin-"+data) ; System.out.println(Thread.currentThread().getName()+ " Put VariableContainer:"+VariableContainer.getThreadInstance().toString()); new A().get() ; new B().get() ; } } }).start() ; } } static class A { public int get(){ //取數據都從當前線程中取得 System.out.println("A from " + Thread.currentThread().getName() + " get data :" + VariableContainer.getThreadInstance().toString()); return data ; } } static class B{ public int get(){ //取數據都從當前線程中取得 System.out.println("B from " + Thread.currentThread().getName() + " get data :" + VariableContainer.getThreadInstance().toString()); return data ; } } } /** * 變量容器 */ class VariableContainer{ private String name ; private int age ; private static ThreadLocal<VariableContainer> threadLocal = new ThreadLocal<VariableContainer>() ; /** * 獲取當前線程中綁定的變量容器 外部可以往容器中設值 */ public static VariableContainer getThreadInstance(){ VariableContainer container = threadLocal.get() ; if(container==null){ container = new VariableContainer() ; threadLocal.set(container) ; } return container ; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "VariableContainer [name=" + name + ", age=" + age + "]"; } }
執行結果:
Thread-0 Put VariableContainer:VariableContainer [name=liaokailin--2103275506, age=-2103275506] A from Thread-0 get data :VariableContainer [name=liaokailin--2103275506, age=-2103275506] B from Thread-0 get data :VariableContainer [name=liaokailin--2103275506, age=-2103275506] Thread-1 Put VariableContainer:VariableContainer [name=liaokailin-816504398, age=816504398] A from Thread-1 get data :VariableContainer [name=liaokailin-816504398, age=816504398] B from Thread-1 get data :VariableContainer [name=liaokailin-816504398, age=816504398]