上一篇部落格講解了,多個執行緒之間的互斥和同步的操作,一個是利用了鎖的技術;另一個內則是利用了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) 變數。這些變數不同於它們的普通對應物,因為訪問某個變數(通過其getset 方法)的每個執行緒都有自己的區域性變數,它獨立於變數的初始化副本。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可以放置變數,也絕對可以放置物件哈!我們把需要放置的變數封裝成一個物件不就可以了嗎?或者封裝到集合,只要打包就可以了哈!具體的程式碼小編就不在多說了。