李青 王江平 朱潔
摘要:多線程編程是Java教學(xué)中的難點,也是影響學(xué)生網(wǎng)絡(luò)編程能力的一個重點。文章針對JaVa程序設(shè)計課中原理闡述不足的問題,嘗試結(jié)合操作系統(tǒng)課程中的部分理論及教學(xué)工具,通過導(dǎo)入線程的概念、繪制狀態(tài)轉(zhuǎn)換圖和設(shè)計同步算法等方式,對Java多線程教學(xué)模式進行研究。
關(guān)鍵詞:多線程編程;Java;操作系統(tǒng);狀態(tài)圖;同步分析
0 引言
Java語言是當(dāng)前最流行的編程語言之一,因為具有跨平臺、面向?qū)ο?、多線程等優(yōu)點,十分適合網(wǎng)絡(luò)時代的編程需要,所以受到市場青睞。目前,我國許多高等院校的理工科專業(yè)都開設(shè)了Java程序沒計課程,該課程的特點是實用性和實踐性強。學(xué)生掌握Java編程能力后,可以完成相應(yīng)的課程設(shè)計,參加大學(xué)生創(chuàng)新、移動編程等競賽,在應(yīng)聘就業(yè)方面也有幫助。這種趨勢既讓更多的學(xué)生對Java產(chǎn)生興趣,同時對Java教學(xué)提出了更高要求。
在Java教學(xué)中,多線程編程是難點,它是初級編程發(fā)展到高級編程的關(guān)鍵,也是開發(fā)實用系統(tǒng)的必備技能。如何理解多線程之間的同步和互斥,理解線程沖突的理論,是教師和學(xué)生共同的難點。多線程編程涉及編程思維的轉(zhuǎn)變,也觸及平臺系統(tǒng)底層的相關(guān)知識,正因為多線程編程復(fù)雜而特殊,所以該知識點不但是難點,也是重點。
1 多線程教學(xué)現(xiàn)狀
Java程序設(shè)計屬于編程課程,因此在教學(xué)中往往側(cè)重語法的傳授和面向?qū)ο缶幊趟枷氲年U述。大多數(shù)相關(guān)教材僅在介紹跨平臺特性的時候,涉及了一些JVM虛擬機的知識,而對平臺系統(tǒng)的底層實現(xiàn)沒有過多講述,因此學(xué)生在學(xué)習(xí)的時候缺少線程概念,更談不上對并發(fā)執(zhí)行的認識
舉一個典型的線程實驗:用不同顏色的小方塊代表運動員,用變量Speed代表速度。方塊的x軸坐標值加上Speed,便可以實現(xiàn)方塊在水平方向的移動。只要學(xué)生掌握線程編寫的方法,知道利用線程來實現(xiàn)方塊類,就能夠完成這個實驗。如果把題目稍加變化,添加一個方向變量Dir。當(dāng)Dir變化時,判斷是增加(減少)小方塊的X軸坐標,還是增加(減少)Y軸坐標,從而把題目變成模擬十字路口的交通。
比較前后兩個實驗,代碼并沒有增加多少,但是學(xué)生在實現(xiàn)模擬交通的時候,不知道如何避免垂直移動的汽車(方塊)和水平移動的汽車(方塊)之間碰撞的情況,這就是現(xiàn)在Java多線程教學(xué)中存在的問題。這個實驗看上去暴露的是學(xué)生對同步問題的欠缺,歸根結(jié)底還是對多線程理解不足。即使把本題中的同步機制告訴學(xué)生,幫助他們解決了模擬交通的實驗題,也不能避免他們被類似問題困擾。
2 改進措施
大學(xué)很多課程之間存在前導(dǎo)后續(xù)的關(guān)系,學(xué)習(xí)的時候應(yīng)該聯(lián)系起來。Java程序設(shè)計的線程理論源自操作系統(tǒng)原理這門課程,因此,可以參考操作系統(tǒng)中線程、狀態(tài)轉(zhuǎn)換和同步這幾個知識點,對Java線程的章節(jié)進行輔助教學(xué)。
2.1 線程概念
進程是操作系統(tǒng)原理課程的知識,參照進程概念的產(chǎn)生和進程的缺陷,可以引出線程的概念,同時闡明線程的優(yōu)點。
在操作系統(tǒng)的發(fā)展歷史中,進程概念的產(chǎn)生源于多任務(wù)設(shè)計?,F(xiàn)代操作系統(tǒng)多是多任務(wù)系統(tǒng),為了充分發(fā)揮其優(yōu)點,編程時一般都考慮多任務(wù)并行執(zhí)行。但是,多進程會造成系統(tǒng)資源的緊張,因為不同的進程都有自己專用的全程環(huán)境。
在這種情況下,即產(chǎn)生了線程的概念。因為一個進程內(nèi)的所有線程共享該進程的所有全程環(huán)境,線程的唯一專用部分是執(zhí)行時使用的寄存器和堆棧。顯然,利用線程可以大大減少系統(tǒng)在任務(wù)切換上花費的時間,從而提高系統(tǒng)的執(zhí)行效率。
2.2 引入狀態(tài)轉(zhuǎn)換圖
在Java線程包的State類中詳細定義和說明了Java線程幾種狀態(tài)(見表1):NEW、RUNNABLE、BLOCKED、WAITING、TIMED—WAITrNG和TERMrNATED。理解這些線程狀態(tài)是學(xué)生掌握線程知識的關(guān)鍵,但是這些狀態(tài)的概念和它們之間復(fù)雜的轉(zhuǎn)換關(guān)系很容易讓學(xué)生困惑,僅僅把狀態(tài)羅列出來加上一些例題演示,并不能揭示其中的關(guān)聯(lián)。
操作系統(tǒng)原理課程中利用狀態(tài)模型繪制三態(tài)圖,很好地解釋了進程狀態(tài)和促發(fā)狀態(tài)轉(zhuǎn)換的事件。教學(xué)中可以參考這個工具,對Java線程的狀態(tài)進行闡述。
如圖1所示,在這張Java的線程狀態(tài)圖中,不但把狀態(tài)的轉(zhuǎn)換標示清楚,而且把引發(fā)原因和對應(yīng)方法也標注出來。例如,新建狀態(tài)(NEW)通過調(diào)用start()方法進入就緒狀態(tài)(RUNNABIE),此外,圖中并無第二處有start()方法,也就意味著不能對已經(jīng)啟動的線程再次調(diào)用start()方法。
當(dāng)系統(tǒng)選定一個就緒的線程對象,它的狀態(tài)就轉(zhuǎn)變成執(zhí)行狀態(tài)(RUNNING),獲得CPU并自動執(zhí)行自己的run0方法。狀態(tài)圖表明,當(dāng)時間片結(jié)束或者線程對象主動執(zhí)行yield()方法,它就會釋放CPU資源,再次轉(zhuǎn)為就緒狀態(tài)。
Java線程的等待狀態(tài)(BIOCKED)情況復(fù)雜,導(dǎo)致線程進入等待的原因有兩類:一是自身需要某個事件響應(yīng),二是出于同步需要而主動等待。Java語言因此提供了很多對應(yīng)的方法。
從運行態(tài)轉(zhuǎn)入阻塞時,如果是因為等待I/O設(shè)備或因為等待其他線程釋放Java對象鎖,則進入BLOCKED狀態(tài),等到設(shè)備空閑或獲得了對象鎖后,線程便可以通過中斷轉(zhuǎn)入就緒狀態(tài),重新等待系統(tǒng)調(diào)度。
同樣是阻塞,如果是因為執(zhí)行了sleep()方法,或帶時間參數(shù)的wait()、join()等方法,則進入TIMED WAITING狀態(tài)。這種狀態(tài)下,線程可以在休眠時間結(jié)束后,自動進入就緒狀態(tài)。
如果因為需要實現(xiàn)同步而在線程內(nèi)執(zhí)行了wait()方法,則線程進入WAITING狀態(tài)。此時,需要同步算法通過notify0、notifyAIIO兩個方法執(zhí)行喚醒操作,才能讓線程重新轉(zhuǎn)為就緒狀態(tài)。
在課堂上用例題講解線程狀態(tài)變化,每題只能說明一種特定的轉(zhuǎn)換,大量冗余的代碼容易讓學(xué)生思路混亂。然而在線程狀態(tài)圖中,眾多的Java方法借助箭頭定位,使復(fù)雜的轉(zhuǎn)換變得直觀形象,更加簡明概括,這種準確性也是狀態(tài)圖的優(yōu)勢。利用狀態(tài)圖引導(dǎo)學(xué)生對線程從創(chuàng)建到結(jié)束的整個生命周期進行推導(dǎo)和回溯,可以有效降低多線程問題講解的難度。
2.3 引入同步的概念
引入狀態(tài)圖后,學(xué)生在學(xué)習(xí)Java線程編程的過程中對線程運行的基本過程有了充分的了解,也能夠理解多線程同時執(zhí)行的原理。但如何控制線程的推進還未接觸,也就對線程沖突的問題缺乏認識。造成線程沖突問題的本質(zhì)是多個線程共享同一資源,同時未能正確地控制執(zhí)行的順序。要讓學(xué)生理解和掌握這部分知識,勢必需要引入同步算法的概念,而這是屬于典型的操作系統(tǒng)課程范疇。
經(jīng)典的同步算法,其本質(zhì)就是讓并發(fā)執(zhí)行的多個線程,對臨界資源互斥訪問,即在某一時問只允許一個線程訪問共享數(shù)據(jù)。操作系統(tǒng)課程中,通過對經(jīng)典同步問題的分析,清晰地揭示了并發(fā)進程在競爭使用的共享資源時導(dǎo)致錯誤的原因。這和Java多線程編程中,多個線程對同一個數(shù)據(jù)一進行操作可能引用沖突的原因是一致的。
操作系統(tǒng)中把這種互斥使用的共享資源命名為臨界資源,對該資源的操作代碼稱為臨界區(qū)。在執(zhí)行臨界區(qū)代碼時,需要進行同步操作。操作系統(tǒng)原理課程中,介紹了同步機制有鎖機制、信號量機制、管程等。這些都是理論知識,具體到Java語言,在同步問題上采用的機制是對象鎖。
這個機制的原理是Java的每個對象都有一個內(nèi)置同步鎖以及等待隊列。Java中可以使用synchronized關(guān)鍵字來取得一個對象的同步鎖。用synchronized關(guān)鍵字修飾的方法和程序塊等價于臨界區(qū),只允許一個線程訪問,其他線程需要進入等待隊列,直到進入的線程退出,釋放對象鎖,這就實現(xiàn)了互斥,而在進入臨界區(qū)后,可以通過wait0和notify0方法進一步實現(xiàn)同步。
以模擬交通的線程實驗為例,垂直和水平兩個方向的道路交匯處,需要輪流通行,才能避免不同方向的汽車碰撞,所以可以判定道路交叉的區(qū)域是一個臨界資源。每次汽車的坐標和這個區(qū)域重合,就需要進行臨界判斷,來決定是否可以進入。具體的算法是,增加一個進入的標志值isBusy,當(dāng)汽車進入交叉區(qū)域時,對isBusy值進行判斷,值為false,則修改isBusy值為ture,進入;否則執(zhí)行wait0方法。同時,當(dāng)汽車離開交叉區(qū)域時,直接修改isBusy值為false,執(zhí)行notify0方法。
3 效果
經(jīng)過上述3個過程,教師在Java多線程教學(xué)中有機地融合操作系統(tǒng)的相關(guān)知識,保持了Java語言編程課程的特色,在項目實驗中向?qū)W生傳授知識。在Java課程中導(dǎo)人操作系統(tǒng)的知識足對大學(xué)課程之間有機結(jié)合的一種嘗試。通過教學(xué)實施中的對比,我們發(fā)現(xiàn)運用這種新的教學(xué)思路,學(xué)生對多線程編程的理解和掌握要明顯優(yōu)于傳統(tǒng)教學(xué)。
從知識掌握的角度來看,學(xué)生了解了多線程出現(xiàn)的前因后果,對線程編程中出現(xiàn)的各類錯誤也能從理解的角度進行改正。通過線程編程實驗,學(xué)生對之前學(xué)習(xí)過的操作系統(tǒng)知識也豁然開朗。兩門不同的課程知識融合在一起,無形中拓展了學(xué)生的知識面,讓他們用更加開闊的視野去理解和學(xué)習(xí)。從學(xué)習(xí)主動性來說,理解了線程轉(zhuǎn)換的理論后,學(xué)生愿意積極主動地嘗試,參考課外資料完善自己的理解,并通過編程驗證這些理論。經(jīng)過這個過程,學(xué)生的自信心得到了極大增強,在后面的學(xué)習(xí)過程中,能夠主動學(xué)習(xí),大膽嘗試從其他課程中尋找答案,甚至有所創(chuàng)新。
4 結(jié)語
電子商務(wù)、電子政務(wù)、微博、頁游這些基于網(wǎng)絡(luò)的事物高速發(fā)展,對程序員Java編程技術(shù)的要求也在不斷提高,這些都促使高校Java教學(xué)的改革,引入新的教學(xué)模式和新的教學(xué)思路。探索在Java多線程的課程和操作系統(tǒng)相關(guān)知識點對接,讓實踐得到理論的指導(dǎo),可以促進學(xué)生從理論水平到實踐能力的全面提升,也對提高Java程序設(shè)計和操作系統(tǒng)原理兩門課程的教學(xué)效果有啟發(fā)作用。這種不同課程之間對接學(xué)習(xí)的方法可以幫助學(xué)生形成連貫完整的知識體系,明確知識和技能之間的前導(dǎo)后續(xù)關(guān)系,最終構(gòu)建一套立體的課程體系。