java併發程式設計(對於執行緒內共享變數安全的思考)
上一篇部落格講解了,多個執行緒之間的互斥和同步的操作,一個是利用了鎖的技術;另一個內則是利用了Object的notify和wait來實現同步操作。這篇部落格呢,來談一下對於執行緒內變數的安全問題。
經典的三層架構,我們都應該比較的熟知,分別是表現層—業務邏輯層——資料訪問層。那麼問題來了,我們如何來保證我們的業務邏輯層來維持同一個資料庫連線物件呢?
<span style="font-family:Comic Sans MS;font-size:18px;">package com.test; import java.util.Random; public class ThreadScopeShareData { //用一個物件來模仿,資料庫連線物件操作 private static int data = 0; public static void main(String[] args) { //迴圈方式共啟動8個執行緒 for(int i=0;i<8;i++){ //啟動一個執行緒 new Thread(new Runnable(){ @Override public void run() { //每次都會讀取一個新的隨機數,類似於每次都會去資料庫獲取一個新的操作 data = new Random().nextInt(); System.out.println(Thread.currentThread().getName() + " has put data :" + data); new A().get(); new B().get(); } }).start(); } } static class A{ public void get(){ System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); } } static class B{ public void get(){ System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data); } } } </span>
上述例子總,一共啟動了8個執行緒,每次都會去獲取新的隨機數,類似於我們每次都會獲取新的資料庫連線物件操作。針對於上面的例子,可想而知,肯定是不可以的,保證不了執行緒內變數的安全,那麼我們怎麼辦呢?
想法一
把每個執行緒的變數單獨放置在一個地方,獲取的時候,根據執行緒的標識去獲取。說白了就是把每個執行緒建立的變數單獨存起來,找的時候,還找他。因此我們可以採取map的方式來實現,也就這樣來操作。
private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>();
具體看下面的程式碼
<span style="font-family:Comic Sans MS;font-size:18px;">package com.test; import java.util.HashMap; import java.util.Map; import java.util.Random; public class ThreadScopeShareData { private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>(); public static void main(String[] args) { //共啟動2個執行緒 for(int i=0;i<8;i++){ //啟動一個執行緒 new Thread(new Runnable(){ @Override public void run() { int data = new Random().nextInt(); System.out.println(Thread.currentThread().getName() + " has put data :" + data); //以當前執行緒為key值放入到map中,當取值時根據各自的執行緒取各自的資料 threadData.put(Thread.currentThread(), data); new A().get(); new B().get(); } }).start(); } } static class A{ public void get(){ int data = threadData.get(Thread.currentThread()); System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); } } static class B{ public void get(){ int data = threadData.get(Thread.currentThread()); System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data); } } } </span>
上面就是把每個執行緒建立的變數放置到單獨的一個地方,也就是map中,到時候根據執行緒標識去map中尋找。
想法二
其實在java中已經為我們提供好了類 ThreadLocal<T>,該類提供了執行緒區域性 (thread-local) 變數。這些變數不同於它們的普通對應物,因為訪問某個變數(通過其get 或set 方法)的每個執行緒都有自己的區域性變數,它獨立於變數的初始化副本。ThreadLocal 例項通常是類中的 private static 欄位,它們希望將狀態與某一個執行緒(例如,使用者 ID 或事務 ID)相關聯。
有了ThreadLocal,上面的程式碼就變得容易起來,直接呼叫相應的set和get方法即可。
<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;
import java.util.Random;
public class ThreadScopeShareData {
//通過ThreadLocal來存取執行緒內的變數
private static ThreadLocal<Integer> threadData=new ThreadLocal<Integer>();
public static void main(String[] args) {
//共啟動8個執行緒
for(int i=0;i<8;i++){
//啟動一個執行緒
new Thread(new Runnable(){
@Override
public void run() {
int data = new Random().nextInt();
System.out.println(Thread.currentThread().getName()
+ " has put data :" + data);
//向當前執行緒中放置相應的區域性變數
threadData.set(data);
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
//從當前執行緒中進行獲取放置的執行緒變數
int data = threadData.get();
System.out.println("A from " + Thread.currentThread().getName()
+ " get data :" + data);
}
}
static class B{
public void get(){
int data = threadData.get();
System.out.println("B from " + Thread.currentThread().getName()
+ " get data :" + data);
}
}
}
</span>
通過ThreadLocal,就可以方便的實現,多個執行緒之間的變數的安全,但是,我們也發現一個ThreadLocal只能存取一個變數,那麼多個變數如何來操作呢?
這時候我們換一個思路不就可以了嗎?ThreadLocal可以放置變數,也絕對可以放置物件哈!我們把需要放置的變數封裝成一個物件不就可以了嗎?或者封裝到集合,只要打包就可以了哈!具體的程式碼小編就不在多說了。