## Java編程那些事兒99——多線程問題及處理2
陳躍峰
出自:[http://blog.csdn.net/mailbomb](http://blog.csdn.net/mailbomb)
如果這個例子還不能幫助你理解如何解決多線程的問題,那么下面再來看一個更加實際的例子——衛生間問題。
例如火車上車廂的衛生間,為了簡單,這里只模擬一個衛生間,這個衛生間會被多個人同時使用,在實際使用時,當一個人進入衛生間時則會把衛生間鎖上,等出來時打開門,下一個人進去把門鎖上,如果有一個人在衛生間內部則別人的人發現門是鎖的則只能在外面等待。從編程的角度來看,這里的每個人都可以看作是一個線程對象,而這個衛生間對象由于被多個線程訪問,則就是臨界資源,在一個線程實際使用時,使用synchronized關鍵將臨界資源鎖定,當結束時,釋放鎖定。實現的代碼如下:
package syn3;
~~~~
/**
?* 測試類
?*/
public class TestHuman {
???????? public static void main(String[] args) {
?????????????????? Toilet t = new Toilet(); //衛生間對象
?????????????????? Human h1 = new Human("1",t);
?????????????????? Human h2 = new Human("2",t);
?????????????????? Human h3 = new Human("3",t);
???????? }
}
package syn3;
/**
?* 人線程類,演示互斥
?*/
public class Human extends Thread {
???????? Toilet t;
???????? String name;
???????? public Human(String name,Toilet t){
?????????????????? this.name = name;
?????????????????? this.t = t;
?????????????????? start(); //啟動線程
???????? }
????????
???????? public void run(){
?????????????????? //進入衛生間
?????????????????? t.enter(name);
???????? }
}
package syn3;
/**
?* 衛生間,互斥的演示
?*/
public class Toilet {
???????? public synchronized void enter(String name){
?????????????????? System.out.println(name + "已進入!");
?????????????????? try{
??????????????????????????? Thread.sleep(2000);
?????????????????? }catch(Exception e){}
?????????????????? System.out.println(name + "離開!");
???????? }
}
~~~
該示例的執行結果為,不同次數下執行結果會有所不同:
1已進入!
1離開!
3已進入!
3離開!
2已進入!
2離開!
在該示例代碼中,Toilet類表示衛生間類,Human類模擬人,是該示例中的線程類,TestHuman類是測試類,用于啟動線程。在TestHuman中,首先創建一個Toilet類型的對象t,并將該對象傳遞到后續創建的線程對象中,這樣后續的線程對象就使用同一個Toilet對象,該對象就成為了臨界資源。下面創建了三個Human類型的線程對象,每個線程具有自己的名稱name參數,模擬3個線程,在每個線程對象中,只是調用對象t中的enter方法,模擬進入衛生間的動作,在enter方法中,在進入時輸出調用該方法的線程進入,然后延遲2秒,輸出該線程離開,然后后續的一個線程進入,直到三個線程都完成enter方法則程序結束。
在該示例中,同一個Toilet類的對象t的enter方法由于具有synchronized修飾符修飾,則在多個線程同時調用該方法時,如果一個線程進入到enter方法內部,則為對象t上鎖,直到enter方法結束以后釋放對該對象的鎖定,通過這種方式實現無論多少個Human類型的線程,對于同一個對象t,任何時候只能有一個線程執行enter方法,這就是解決多線程問題的第一種思路——互斥的解決原理。
### 12.4.2 同步
使用互斥解決多線程問題是一種簡單有效的解決辦法,但是由于該方法比較簡單,所以只能解決一些基本的問題,對于復雜的問題就無法解決了。
解決多線程問題的另外一種思路是同步。同步是另外一種解決問題的思路,結合前面衛生間的示例,互斥方式解決多線程的原理是,當一個人進入到衛生間內部時,別的人只能在外部時刻等待,這樣就相當于別的人雖然沒有事情做,但是還是要占用別的人的時間,浪費系統的執行資源。而同步解決問題的原理是,如果一個人進入到衛生間內部時,則別的人可以去睡覺,不占用系統資源,而當這個人從衛生間出來以后,把這個睡覺的人叫醒,則它就可以使用臨界資源了。所以使用同步的思路解決多線程問題更加有效,更加節約系統的資源。
在常見的多線程問題解決中,同步問題的典型示例是“生產者-消費者”模型,也就是生產者線程只負責生產,消費者線程只負責消費,在消費者發現無內容可消費時則睡覺。下面舉一個比較實際的例子——生活費問題。
生活費問題是這樣的:學生每月都需要生活費,家長一次預存一段時間的生活費,家長和學生使用統一的一個帳號,在學生每次取帳號中一部分錢,直到帳號中沒錢時通知家長存錢,而家長看到帳戶還有錢則不存錢,直到帳戶沒錢時才存錢。在這個例子中,這個帳號被學生和家長兩個線程同時訪問,則帳號就是臨界資源,兩個線程是同時執行的,當每個線程發現不符合要求時則等待,并釋放分配給自己的CPU執行時間,也就是不占用系統資源。實現該示例的代碼為:
package syn4;
~~~
/**
?* 測試類
?*/
public class TestAccount {
???????? public static void main(String[] args) {
?????????????????? Accout a = new Accout();
?????????????????? StudentThread s = new StudentThread(a);
?????????????????? GenearchThread g = new GenearchThread(a);
???????? }
}
package syn4;
/**
?* 模擬學生線程
?*/
public class StudentThread extends Thread {
???????? Accout a;
???????? public StudentThread(Accout a){
?????????????????? this.a = a;
?????????????????? start();
???????? }
???????? public void run(){
?????????????????? try{
??????????????????????????? while(true){
???????????????????????????????????? Thread.sleep(2000);
???????????????????????????????????? a.getMoney();? //取錢
??????????????????????????? }
?????????????????? }catch(Exception e){}
???????? }
}
package syn4;
/**
?* 家長線程
?*/
public class GenearchThread extends Thread {
???????? Accout a;
???????? public GenearchThread(Accout a){
?????????????????? this.a = a;
?????????????????? start();
???????? }
???????? public void run(){
?????????????????? try{
??????????????????????????? while(true){
???????????????????????????????????? Thread.sleep(12000);
???????????????????????????????????? a.saveMoney();? //存錢
??????????????????????????? }
?????????????????? }catch(Exception e){}
???????? }
}
package syn4;
/**
?* 銀行賬戶
?*/
public class Accout {
???????? int money = 0;
???????? /**
???????? ?* 取錢
???????? ?* 如果賬戶沒錢則等待,否則取出所有錢提醒存錢
???????? ?*/
???????? public synchronized void getMoney(){
?????????????????? System.out.println("準備取錢!");
?????????????????? try{
??????????????????????????? if(money == 0){
???????????????????????????????????? wait();? //等待
??????????????????????????? }
??????????????????????????? //取所有錢
??????????????????????????? System.out.println("剩余:" + money);
??????????????????????????? money -= 50;
??????????????????????????? //提醒存錢
??????????????????????????? notify();
?????????????????? }catch(Exception e){}????????????????
???????? }
????????
???????? /**
???????? ?* 存錢
???????? ?* 如果有錢則等待,否則存入200提醒取錢
???????? ?*/
???????? public synchronized void saveMoney(){
?????????????????? System.out.println("準備存錢!");
?????????????????? try{
??????????????????????????? if(money != 0){
???????????????????????????????????? wait();? //等待
??????????????????????????? }
??????????????????????????? //取所有錢
??????????????????????????? money = 200;
??????????????????????????? System.out.println("存入:" + money);
??????????????????????????? //提醒存錢
??????????????????????????? notify();
?????????????????? }catch(Exception e){}????????????????
???????? }
}
~~~
該程序的一部分執行結果為:
準備取錢!
準備存錢!
存入:200
剩余:200
準備取錢!
剩余:150
準備取錢!
剩余:100
準備取錢!
剩余:50
準備取錢!
準備存錢!
存入:200
剩余:200
準備取錢!
剩余:150
準備取錢!
剩余:100
準備取錢!
剩余:50
準備取錢!
在該示例代碼中,TestAccount類是測試類,主要實現創建帳戶Account類的對象,以及啟動學生線程StudentThread和啟動家長線程GenearchThread。在StudentThread線程中,執行的功能是每隔2秒中取一次錢,每次取50元。在GenearchThread線程中,執行的功能是每隔12秒存一次錢,每次存200。這樣存款和取款之間不僅時間間隔存在差異,而且數量上也會出現交叉。而該示例中,最核心的代碼是Account類的實現。
在Account類中,實現了同步控制功能,在該類中包含一個關鍵的屬性money,該屬性的作用是存儲帳戶金額。在介紹該類的實現前,首先介紹一下兩個同步方法——wait和notify方法的使用,這兩個方法都是Object類中的方法,也就是說每個類都包含這兩個方法,換句話說,就是Java天生就支持同步處理。這兩個方法都只能在synchronized修飾的方法或語句塊內部采用被調用。其中wait方法的作用是使調用該方法的線程休眠,也就是使該線程退出CPU的等待隊列,處于冬眠狀態,不執行動作,也不占用CPU排隊的時間,notify方法的作用是喚醒一個任意該對象的線程,該線程當前處于休眠狀態,至于喚醒的具體是那個則不保證。在Account類中,被StudentThread調用的getMoney方法的功能是判斷當前金額是否是0,如果是則使StudentThread線程處于休眠狀態,如果金額不是0,則取出50元,同時喚醒使用該帳戶對象的其它一個線程,而被GenearchThread線程調用的saveMoney方法的功能是判斷當前是否不為0,如果是則使GenearchThread線程處于休眠狀態,如果金額是0,則存入200元,同時喚醒使用該帳戶對象的其它一個線程。
如果還是不清楚,那就結合前面的程序執行結果來解釋一下程序執行的過程:在程序開始執行時,學生線程和家長線程都啟動起來,所以輸出“準備取錢”和“準備存錢”,然后學生線程按照該線程run方法的邏輯執行,先延遲2秒,然后調用帳戶對象a中的getMoney方法,但是由于初始情況下帳戶對象a中的money數值為0,所以學生線程就休眠了。在學生線程執行的同時,家長線程也按照該線程的run方法的邏輯執行,先延遲12秒,然后調用帳戶對象a中的saveMoney方法,由于帳戶a對象中的money為零,條件不成立,所以執行存入200元,同時喚醒線程,由于使用對象a的線程現在只有學生線程,所以學生線程被喚醒,開始執行邏輯,取出50元,然后喚醒線程,由于當前沒有線程處于休眠狀態,所以沒有線程被喚醒。同時家長線程繼續執行,先延遲12秒,這個時候學生線程執行了4次,耗時4X2秒=8秒,就取光了帳戶中的錢,接著由于帳戶為0則學生線程又休眠了,一直到家長線程延遲12秒結束以后,判斷帳戶為0,又存入了200元,程序繼續執行下去。
在解決多線程問題是,互斥和同步都是解決問題的思路,如果需要形象的比較這兩種方式的區別的話,就看一下下面的示例。一個比較忙的老總,桌子上有2部電話,在一部處于通話狀態時,另一部響了,老總拿其這部電話說我在接電話,你等一下,而沒有掛電話,這種處理的方式就是互斥。而如果老總拿其另一部電話說,我在接電話,等會我打給你,然后掛了電話,這種處理的方式就是同步。兩者相比,互斥明顯占用系統資源(浪費電話費,浪費別人的時間),而同步則是一種更加好的解決問題的思路。
- 前言
- (1)序言
- (2)程序設計是什么?
- (3)你適合學習程序設計嗎?
- (4)如何學好程序設計?
- (5)程序設計介紹小結
- (6)計算機軟件基本概念
- (7)進制的概念
- (8)計算機內部的數據表達
- (9)網絡編程基礎
- (10)Java語言簡介
- (11)JDK的獲得、安裝和配置
- (12)第一個HelloWorld程序
- (13)Eclipse基本使用
- (14)Eclipse基礎使用進階
- (15)如何學好Java語法
- (16)代碼框架、關鍵字和標識符
- (17)基本數據類型
- (18)變量和常量
- (19)數據類型轉換
- (20)空白、語句結束和注釋
- (21)算術運算符
- (22)比較運算符
- (23)邏輯運算符
- (24)賦值運算符
- (25)位運算符
- (26)移位運算符
- (27)其它運算符
- (28)運算符優先級
- (29)表達式
- (30)流程控制基礎
- (31)if語句語法(1)
- (32)if語句語法(2)
- (33)if語句語法(3)
- (34)switch語句語法
- (35)while語句語法
- (36)do-while語句語法
- (37)for語句語法
- (38)break和continue語句
- (39)流程控制綜合示例1
- (40)流程控制綜合示例2
- (41)流程控制綜合示例3
- (42)流程控制綜合練習
- (43)數組概述
- (44)數組基礎語法
- (45)數組使用示例1
- (46)數組使用示例2
- (47)數組使用示例3
- (48)多維數組基礎
- (49)多維數組使用示例1
- (50)多維數組使用示例2
- (51)多維數組練習
- (52)方法聲明
- (53)方法聲明示例
- (54)方法調用
- (55)方法重載和參數傳遞
- (56)方法練習
- (57)面向對象基礎
- (58)類(一)
- (59)類(二)
- (60)對象
- (61)面向對象設計方法和面向對象特性(一)
- (62)繼承(二)
- (63)多態性
- (64)訪問控制符、修飾符和其它關鍵字
- (65)static修飾符
- (66)final修飾符
- (67)this和super
- (68)抽象類和接口(一)
- (69)抽象類和接口(二)
- (70)抽象類和接口(三)
- (71)內部類簡介
- (72)包的概念
- (73)JDK文檔使用
- (74)java.lang包介紹1
- (75)String類使用
- (76)StringBuffer類和System類
- (77)包裝類
- (78)時間和日期處理
- (79)Random隨機處理
- (80)集合框架簡述
- (81)異常處理概述
- (82)異常處理語法1
- (83)異常處理語法2
- (84)IO簡介
- (85)IO類體系
- (86)文件操作之File類使用
- (87)文件操作之讀取文件
- (88)文件操作之寫文件
- (89)讀取控制臺輸入
- (90)裝飾流使用1
- (91)裝飾流使用2
- (92)IO使用注意問題
- (93)多線程基礎
- (94)多線程實現方式1
- (95)多線程實現方式2
- (96)多線程使用示例1
- (97)多線程使用示例2
- (98)多線程問題及處理1
- (99)多線程問題及處理2
- (100)多線程問題及處理3
- (101)網絡編程概述
- (102)網絡編程技術1
- (103)網絡編程技術2
- (104)網絡編程技術3
- (105)網絡編程技術4
- (106)網絡編程技術5
- (107)網絡協議概念
- (108)網絡編程示例1
- (109)網絡編程示例2
- (110)網絡編程小結