轉:Java編程那些事兒98——多線程問題及處理1

 

原創 Java編程那些事兒98——多線程問題及處理1收藏
function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}
Java編程那些事兒98——多線程問題及處理1
陳躍峯
12.4 多線程問題及處理
         多線程編程爲程序開發帶來了很多的方便,但是也帶來了一些問題,這些問題是在程序開發過程中必須進行處理的問題。
         這些問題的核心是,如果多個線程同時訪問一個資源,例如變量、文件等,時如何保證訪問安全的問題。在多線程編程中,這種會被多個線程同時訪問的資源叫做臨界資源。
         下面通過一個簡單的示例,演示多個線程訪問臨界資源時產生的問題。在該示例中,啓動了兩個線程類DataThread的對象,該線程每隔200毫秒輸出一次變量n的值,並將n的值減少1。變量n的值存儲在模擬臨界資源的Data類中,該示例的核心是兩個線程類都使用同一個Data類的對象,這樣Data類的這個對象就是一個臨界資源了。示例代碼如下:
                   package syn1;
/**
 * 模擬臨界資源的類
 */
public class Data {
         public int n;
         public Data(){
                   n = 60;
         }
}
package syn1;
/**
 * 測試多線程訪問時的問題
 */
public class TestMulThread1 {
         public static void main(String[] args) {
                   Data data = new Data();
                   DataThread d1 = new DataThread(data,"線程1");
                   DataThread d2 = new DataThread(data,"線程2");
         }
}
package syn1;
/**
 * 訪問數據的線程
 */
public class DataThread extends Thread {
         Data data;
         String name;
         public DataThread(Data data,String name){
                   this.data = data;
                   this.name = name;
                   start();
         }
          
         public void run(){
                   try{
                            for(int i = 0;i < 10;i++){
                                     System.out.println(name + ":" + data.n);
                                     data.n--;
                                     Thread.sleep(200);
                            }
                   }catch(Exception e){}
         }
}
         在運行時,因爲不同情況下該程序的運行結果會出現不同,該程序的一種執行結果爲:
                   線程1:60
線程2:60
線程2:58
線程1:58
線程2:56
線程1:56
線程2:54
線程1:54
線程2:52
線程1:52
線程2:50
線程1:50
線程2:48
線程1:48
線程2:47
線程1:46
線程2:44
線程1:44
線程2:42
線程1:42
         從執行結果來看,第一次都輸出60是可以理解的,因爲線程在執行時首先輸出變量的值,這個時候變量n的值還是初始值60,而後續的輸出就比較麻煩了,在開始的時候兩個變量保持一致的輸出,而不是依次輸出n的每個值的內容,而到將要結束時,線程2輸出47這個中間數值。
         出現這種結果的原因很簡單:線程1改變了變量n的值以後,還沒有來得及輸出,這個變量n的值就被線程2給改變了,所以在輸出時看的輸出都是跳躍的,偶爾出現了連續。
         出現這個問題也比較容易接受,因爲最基本的多線程程序,系統只保證線程同時執行,至於哪個先執行,哪個後執行,或者執行中會出現一個線程執行到一半,就把CPU的執行權交給了另外一個線程,這樣線程的執行順序是隨機的,不受控制的。所以會出現上面的結果。
         這種結果在很多實際應用中是不能被接受的,例如銀行的應用,兩個人同時取一個賬戶的存款,一個使用存摺、一個使用卡,這樣訪問賬戶的金額就會出現問題。或者是售票系統中,如果也這樣就出現有人買到相同座位的票,而有些座位的票卻未售出。
         在多線程編程中,這個是一個典型的臨界資源問題,解決這個問題最基本,最簡單的思路就是使用同步關鍵字synchronized
         synchronized關鍵字是一個修飾符,可以修飾方法或代碼塊,其的作用就是,對於同一個對象(不是一個類的不同對象),當多個線程都同時調用該方法或代碼塊時,必須依次執行,也就是說,如果兩個或兩個以上的線程同時執行該段代碼時,如果一個線程已經開始執行該段代碼,則另外一個線程必須等待這個線程執行完這段代碼才能開始執行。就和在銀行的櫃檯辦理業務一樣,營業員就是這個對象,每個顧客就好比線程,當一個顧客開始辦理時,其它顧客都必須等待,及時這個正在辦理的顧客在辦理過程中接了一個電話 (類比於這個線程釋放了佔用CPU的時間,而處於阻塞狀態),其它線程也只能等待。
         使用synchronized關鍵字修改以後的上面的代碼爲:
                   package syn2;
/**
 * 模擬臨界資源的類
 */
public class Data2 {
         public int n;
         public Data2(){
                   n = 60;
         }
        
         public synchronized void action(String name){
                   System.out.println(name + ":" + n);
                   n--;
         }
}
package syn2;
/**
 * 測試多線程訪問時的問題
 */
public class TestMulThread2 {
         public static void main(String[] args) {
                   Data2 data = new Data2();
                   Data2Thread d1 = new Data2Thread(data,"線程1");
                   Data2Thread d2 = new Data2Thread(data,"線程2");
         }
}
package syn2;
/**
 * 訪問數據的線程
 */
public class Data2Thread extends Thread {
         Data2 data;
         String name;
         public Data2Thread(Data2 data,String name){
                   this.data = data;
                   this.name = name;
                   start();
         }
          
         public void run(){
                   try{
                            for(int i = 0;i < 10;i++){
                                     data.action(name);
                                     Thread.sleep(200);
                            }
                   }catch(Exception e){}
         }
}
         該示例代碼的執行結果會出現不同,一種執行結果爲:
                   線程1:60
線程2:59
線程2:58
線程1:57
線程2:56
線程1:55
線程2:54
線程1:53
線程2:52
線程1:51
線程2:50
線程1:49
線程1:48
線程2:47
線程2:46
線程1:45
線程2:44
線程1:43
線程2:42
線程1:41
         在該示例中,將打印變量n的代碼和變量n變化的代碼組成一個專門的方法action,並且使用修飾符synchronized修改該方法,也就是說對於一個Data2的對象,無論多少個線程同時調用action方法時,只有一個線程完全執行完該方法以後,別的線程才能夠執行該方法。這就相當於一個線程執行到該對象的synchronized方法時,就爲這個對象加上了一把鎖,鎖住了這個對象,別的線程在調用該方法時,發現了這把鎖以後就繼續等待下去了。
         其實這只是一種簡單的方法,使用這種方法雖然解決了同時訪問的問題,但是卻使得線程的執行是無序的,從執行結果可以看出,線程的執行是交錯的。就好象在銀行辦理業務一樣,一大羣人圍在窗口,一個人在辦理時其他的人就處於等待,但是當這個人辦理完成以後其他的人那個先辦理就看誰能搶到了。那如果想讓線程有序執行,就是讓線程執行時依次執行,就像銀行中排隊辦理業務一樣,又該怎麼解決呢?這個就是線程的另外一種解決思路了,比較著名的例子是生產者-消費者模型,這裏就不再重複了,下面舉一個淺顯的例子來說明這個問題,並使用代碼實現該示例.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章