歡迎關注個人微信公衆號:程序員私房菜(id:eson_15)java
假設如今有個公共的變量 data,有不一樣的線程均可以去操做它,若是在不一樣的線程對 data 操做完成後再去取這個 data,那麼確定會出現線程間的數據混亂問題,由於 A 線程在取 data 數據前可能 B 線程又對其進行了修改,下面寫個程序來講明一下該問題:程序員
public class ThreadScopeShareData {
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() {
int temp = new Random().nextInt();
System.out.println(Thread.currentThread().getName() + " has put a data: " + temp); //打印出來爲了看效果
data = temp; //操做數據:賦新值
new TestA().getData();
new TestB().getData();
}
}).start();
}
}
static class TestA {
public void getData() {
System.out.println("A get data from " + Thread.currentThread().getName() + ": " + data);//取出公共數據data
}
}
static class TestB {
public void getData() {
System.out.println("B get data from " + Thread.currentThread().getName() + ": " + data);
}
}
}
複製代碼
來看一下打印出來的結果:數據庫
Thread-0 has put a data: -1885917900
Thread-1 has put a data: -1743455464
A get data from Thread-0: -1743455464
A get data from Thread-1: -1743455464
B get data from Thread-1: -1743455464
B get data from Thread-0: -1743455464安全
從結果中能夠看出,兩次對 data 賦的值確實不同,可是兩個線程最後打印出來的都是最後賦的那個值,說明 Thread-0 拿出的數據已經不對了,這就是線程間共享數據帶來的問題。微信
固然,咱們徹底可使用 synchronized 關鍵字將 run()
方法中的幾行代碼給套起來,這樣每一個線程各自執行完,打印出各自的信息,這是沒問題的,確實能夠解決上面的線程間共享數據問題。可是,這是以其餘線程被阻塞爲代價的,即 Thread-0 在執行的時候,Thread-1 就被阻塞了,必須等待 Thread-0 執行完了才能執行。併發
那麼若是我想兩個線程同時跑,而且互不影響各自取出的值,該怎麼辦呢?這也是本文所要總結的重點,解決該問題的思想是:雖然如今都在操做公共數據 data,可是不一樣的線程自己對這個 data 要維護一個副本,這個副本不是線程間所共享的,而是每一個線程所獨有的,因此不一樣線程中所維護的 data 是不同的,最後取的時候,是哪一個線程,我就從哪一個線程中取該 data。dom
基於上面這個思路,我再把上面的程序作一修改,以下:ide
public class ThreadScopeShareData {
private static int data = 0;//公共的數據
//定義一個Map以鍵值對的方式存儲每一個線程和它對應的數據,即Thread:data
private static Map<Thread, Integer> threadData = Collections.synchronizedMap(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() {
int temp = new Random().nextInt();
System.out.println(Thread.currentThread().getName() + " has put a data: " + temp); //打印出來爲了看效果
threadData.put(Thread.currentThread(), temp); //向Map中存入本線程data數據的一個副本
data = temp; //操做數據:賦新值
new TestA().getData();
new TestB().getData();
}
}).start();
}
}
static class TestA {
public void getData() {
System.out.println("A get data from " + Thread.currentThread().getName() + ": "
+ threadData.get(Thread.currentThread())); //取出各線程維護的那個副本
}
}
static class TestB {
public void getData() {
System.out.println("B get data from " + Thread.currentThread().getName() + ": "
+ threadData.get(Thread.currentThread()));
}
}
}
複製代碼
上面程序中維護了一個 Map,鍵值對分別是線程和它的數據,那麼在操做 data 的時候,先把各自的數據保存到這個 Map 中,這樣每一個線程保存的確定不一樣,當再取的時候,根據當前線程對象做爲 key 來取出對應的 data 副本,這樣不一樣的線程之間就不會相互影響了。這個 HashMap 也須要包裝一下,由於 HashMap 是非線程安全的,上面的程序中,不一樣的線程有對 HashMap 進行寫操做,就有可能產生併發問題,因此也要包裝一下。最後來看一下執行結果:學習
Thread-0 has put a data: 1817494992
Thread-1 has put a data: -1189758355
A get data from Thread-0: 1817494992
A get data from Thread-1: -1189758355
B get data from Thread-0: 1817494992
B get data from Thread-1: -1189758355spa
就是線程範圍內共享數據,即同一個線程裏面這個數據是共享的,線程間是不共享的。
這讓我聯想到了學習數據庫的時候用到的 ThreadLocal,操做數據庫須要 connection,若是當前線程中有就拿當前線程中存的 connection,不然就新建一個放到當前線程中,這樣就不會出現問題,由於每一個線程自己共享了一個 connection,它不是線程間共享的。這也很好理解,這個 connection 確定不能共享,假設 A 和 B 用戶都拿到這個 connection 並開啓了事務,如今 A 開始轉帳了,可是錢還沒轉好,B 轉好了關閉了事務,那麼A那邊就出問題了。
線程範圍內共享數據的問題就總結這麼多吧~若是有問題,歡迎指正,咱們一塊兒進步!
也歡迎你們關注個人微信公衆號:程序員私房菜。我會持續輸出更多文章。