## Java編程那些事兒98——多線程問題及處理1
陳躍峰
出自:[http://blog.csdn.net/mailbomb](http://blog.csdn.net/mailbomb)
### 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方法時,就為這個對象加上了一把鎖,鎖住了這個對象,別的線程在調用該方法時,發現了這把鎖以后就繼續等待下去了。
?
- 前言
- (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)網絡編程小結