
圖5.5展示了不同線程對同一數據進行存取的兩種情況。對情況a),因只存在讀操作,類似多人看報,各線程獲取的數據是確定的;對情況b),因有寫操作,當線程以不同順序執行時,D讀取到的值、x的最終值是不確定的。如以D-E-F次序執行,D讀到1,x值為3;若以F-E-D次序執行,D讀到2,x值為2。
這種多個線程對同一數據的並發讀寫(至少有一個線程執行寫操作)被稱作競爭。競爭會導致數據的不確定性。這種不確定性,有資料將其視為數據訪問不安全。
下面以銀行存取錢為例介紹上述不確定性必須要被解決。假設銀行有賬戶張三,賬戶餘額100元。現張三及其兒子同時在不同存取款一體機上對該賬戶進行操作。張三存入200元,其兒子取出300元。假設使用存取一體機時,必須經歷{查-改-查}三個步驟。為在本地顯示餘額和修改金額,一體機上需要有這兩個本地變量:餘額和輸入的數據,見圖5.6。為方便分析,圖中對不同位置上的數據和指令做了不同標註,用c/q表示輸入的存錢/取錢金額;cm/qm、m分別表示本地緩存餘額和賬戶餘額;c1、q1等表示不同機器上的動作。
根據需求,有m=100,c=200,q=300。假設執行序列為{ q1-c1-q2-c2-q3-c3},對應的指令序列:qm=m; cm=m; m=qm-q; m=cm+c; qm=m; cm=m; 代入數據,結果如下:
qm=100; cm=100; m=100-300=-200;m=100+200=300;qm=300;cm=300
換言之,賬戶原有100元,存200,取300,執行完畢後,最終餘額300。銀行顯然不能容忍。可能的執行序列很多,這裡不再贅述,請讀者自行分析。
上述現象是{c1-c2-c3}和{q1-q2-q3}以交錯方式對同一賬戶競爭存取所致。若二者以「不可分割」的方式(也稱獨占方式或互斥方式)執行,如現存後取,或是先取後存,則不會出現上述狀況。Java用synchronized( D ){ S }框架實現互斥。其中對象D稱作臨界資源,代碼段S稱作臨界區。該框架表示:S只能以原子(即不可分割)方式訪問D。
01
線程的互斥機制-示例
【例5.5】假設賬戶張三有餘額100元,對賬戶的存取錢過程的動作序列均為「查-改-查」。需要存入200,取出300。藉助線程機制,模擬對張三的賬戶同時實施存取。
目的:掌握互斥機制的實現和應用框架。
設計:本例主要設計了兩個類:賬戶類Accoun、實施存/取錢的ATM類。其中:
Account類有屬性:姓名、金額,用構造函數對這些屬性賦值,以及對金額的read/write方法、獲取姓名的 getName()方法。
ATM類涉及對賬戶存/取特定金額,故有兩個屬性:賬戶a、存/取金額atmVal(正數表示存錢、負數表示取錢)。為模擬同時執行,ATM類必須是線程類,在run()實施存錢或取錢,基本流程為「查-改-查」。
注意:由於指令執行速度非常快,即使未用互斥框架,線程體運行也可能一次運行完畢(即存錢過程和取錢過程未發生指令交錯)。這樣難以發現設計問題。故線程體中加入了一些sleep()動作。sleep()執行時,當前線程必須放棄處理機控制權轉入休眠,線程切換自然就發生了。因此加入sleep()可模擬線程頻繁切換情形(最壞情形)。這是調試線程的常用策略。另外,sleep()所屬線程會放棄cpu,但不會放棄對資源的占用(即不會打開鎖)。
02
輸出結果
未使用互斥框架的運行結果
張三:現有 100 元,取出300元
張三:現有 100 元,存入200元,現有餘額 300 元。,現有餘額 300 元。
使用互斥框架的運行結果
張三:現有 100 元,取出300元,現有餘額 -200 元。
張三:現有 -200 元,存入200元,現有餘額 0 元。
03
示例剖析
從結果看,未使用互斥框架的輸出信息雜亂,且餘額不對。使用互斥框架後輸出信息完整,結果正確的。注意:sleep(1)表示休眠1毫秒。執行時,線程定會放棄處理機,故能增加線程間的切換次數。本例run()中的sleep(1),旨在模擬出線程執行的最壞情形:線程間頻繁交錯,這有利於線程的調試。由於該方法會產生檢查型異常,故需要對其進行處理。另外,Thread類還有一個靜態方法yield(),又稱讓步方法,能夠讓線程主動放棄CPU,將CPU控制權交給相同或更高優先級的線程。5.3.4節將介紹,由於線程的調度策略,線程的優先級效果似乎不明顯,即yield()的效果也不如sleep()明顯。
互斥實現機理。所有對象上都有一個自動鎖,初始為開啟狀態(unlock),一旦有線程的臨界區訪問該對象(即臨界資源對象),則鎖自動鎖閉(lock),即此線程「持有」鎖。當臨界區執行完畢後,則鎖自動開啟,即此線程「釋放鎖」。臨界資源鎖閉期間,任何其他線程無法再訪問該臨界資源,從而可確保線程間以排它方式執行。對本例而言,首先,atm1、atm2擁有同一臨界資源a(見main方法),這是互斥的基礎。若它們分別關聯不同的線程對象,就不會產生競爭。請讀者驗證之。之後,若atm1首先占有a,則atm2必須等待,直至atm1的臨界區執行完畢,atm2才可能獲得a的對象鎖,繼而執行。若atm2先執行,情況類似。注意,只有對象才有「對象鎖」,換言之,基本型沒有對象鎖,不能作為臨界資源。另外,只有臨界區執行前才會檢查鎖的狀態,sleep()執行時不會釋放鎖。
本例設計的關鍵點有二:1. 在ATM類的run中用synchronized(a){s}框起,確保代碼段s以原子方式執行;2. main中atm1、atm2關聯同一個賬戶a(即使用同一變量a),即確保線程atm1、atm2訪問的臨界資源是相同的,這樣二者才能以互斥方式執行。
實例講解
Java面向對象程序設計
思想·方法·應用

下期預告
模擬生產者-消費者問題
文本框式計算器1.0 2.0
二人間對話示例
04
參考書籍

《Java面向對象程序設計:思想·方法·應用(微課視頻版)》
提供PPT課件,教學大綱,源碼,視頻,諮詢QQ:2301891038(僅限教師)
ISBN:9787302590668
作者:化志章 揭安全 石海鶴 王嵐
定價:59.8元
掃描,優惠購書
內容簡介
本書基於Java語言,以案例為核心,問題求解為主線,快速深入地介紹面向對象程序設計的基本思想、方法和應用,以及GUI編程、線程、IO流等高級應用框架。
全書包括三部分:第1部分Java入門,對應第1章和第2章,討論Java概況、JDK配置,從C過渡到Java,並涉及一些面向對象的基本概念、理念和語法元素;第2部分面向對象程序設計,對應第3章,結合案例,系統闡述面向對象程序設計方法及其語法支撐機制,還包括異常處理、內部類等輔助機制;第3部分實用技術和框架,包括第4~10章,涉及圖形用戶編程、線程機制、IO流、網絡通信、泛型和集合框架、Java連接數據庫、反射機制與代理模式等內容。
本書在內容組織上,基於案例介紹內容,直觀、高效;在內容設計上,所有案例均有目的、設計、源碼和分析,便於快速深入地理解、領會;在內容表述上,結合豐富的圖解和形象的比喻,破解技術難點。各章的章首配有導引,指明本章的設置目的、內容組織的邏輯主線、重點和難點等內容;章末配有小結,進行簡單梳理、提煉;「思考與練習」中提供一組問答題,用於回顧和檢測對前期內容的理解,並提供一些綜合型編程作業。
本書適合作為高等院校計算機、軟件工程專業和各種軟件培訓機構的教材,也特別適合廣大程序員及其他Java開發愛好者自學、參考。
05
精彩推薦