JAVA多線程編程_第1頁
JAVA多線程編程_第2頁
JAVA多線程編程_第3頁
JAVA多線程編程_第4頁
JAVA多線程編程_第5頁
已閱讀5頁,還剩15頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

基于基礎(chǔ)篇 一 基于基礎(chǔ)篇 一 寫在前面寫在前面 隨著計(jì)算機(jī)技術(shù)的發(fā)展 編程模型也越來越復(fù)雜多樣化 但多線程編程模 型是目前計(jì)算機(jī)系統(tǒng)架構(gòu)的最終模型 隨著 CPU 主頻的不斷攀升 X86 架構(gòu)的 硬件已經(jīng)成為瓶 在這種架構(gòu)的 CPU 主頻最高為 4G 事實(shí)上目前 3 6G 主頻的 CPU 已經(jīng)接近了頂峰 如果不能從根本上更新當(dāng)前 CPU 的架構(gòu) 在很長一段時間內(nèi)還不太可能 那么繼續(xù)提高 CPU 性能的方法就是超線程 CPU 模式 那么 作業(yè)系統(tǒng) 應(yīng)用程 序要發(fā)揮 CPU 的最大性能 就是要改變到以多線程編程模型為主的并行處理系 統(tǒng)和并發(fā)式應(yīng)用程序 所以 掌握多線程編程模型 不僅是目前提高應(yīng)用性能的手段 更是下一 代編程模型的核心思想 多線程編程的目的 就是 最大限度地利用 CPU 資 源 當(dāng)某一線程的處理不需要占用 CPU 而只和 I O OEMBIOS 等資源打交道時 讓 需要占用 CPU 資源的其它線程有機(jī)會獲得 CPU 資源 從根本上 說 這就是多線 程編程的最終目的 第一需要弄清的問題第一需要弄清的問題 如同程序和進(jìn)程的區(qū)別 要掌握多線程編程 第一要弄清的問題是 線程線程 對象和線程的區(qū)別對象和線程的區(qū)別 線程對象是可以產(chǎn)生線程的對象 比如在 java 平臺中 Thread 對象 Runnable 對象 線程 是指正在執(zhí)行的一個指點(diǎn)令序列 在 java 平臺上是指 從一個線程對象的 start 開始 運(yùn)行 run 方法體中的那一段相對獨(dú)立的過程 鑒于作者的水平 無法用更確切的詞匯來描述它們的定義 但這兩個有本 質(zhì)區(qū)別的概念請初學(xué)者細(xì)細(xì)體會 隨著介紹的深入和例程分析的增加 就會慢 慢明白它們所代表的真實(shí)含義 天下難事必始于易 天下大事必始于細(xì) 讓我們先從最簡單的 單線程 來入手 1 帶引號說明只是相對而言的單線 程 2 基于 java class BeginClass public static void main String args for int i 0 i 100 i System out println Hello World 如果我們成功編譯了該 java 文件 然后在命令行上敲入 java BeginClass 現(xiàn)在發(fā)生了什么呢 每一個 java 程序員 從他開始學(xué)習(xí) java 的第一分鐘 里都會接觸到這個問 題 但是 你知道它到底發(fā)生發(fā)什么 JVM 進(jìn)程被啟動 在同一個 JVM 進(jìn)程中 有且只有一個進(jìn)程 就是它自己 然后在這個 JVM 環(huán)境中 所有程序的運(yùn)行都是以線程來運(yùn)行 JVM 最 先會產(chǎn)生 一個主線程 由它來運(yùn)行指定程序的入口點(diǎn) 在這個程序中 就是主線程從 main 方法開始運(yùn)行 當(dāng) main 方法結(jié)束后 主線程運(yùn)行完成 JVM 進(jìn)程 也隨之 退出 我們看到的是一個主線程在運(yùn)行 main 方法 這樣的只有一個線程執(zhí)行程序 邏輯的流程我們稱 之為單線程單線程 這是 JVM 提供給我們的單線程環(huán)境 事實(shí)上 JVM 底層還至 少有垃圾回收這樣的后臺線程以及其它非 java 線程 但這些線程對我們而言不 可訪問 我們只認(rèn)為它是單線程的 主線程是 JVM 自己啟動的 在這里它不是從線程對象產(chǎn)生的 在這個線程 中 它運(yùn)行了 main 方法這個指令序列 理解它 但它沒有更多可以研究的內(nèi)容 接觸多線程接觸多線程 class MyThread extends Thread public void run System out println Thread say Hello World public class MoreThreads public static void main String args new MyThread new MyThread start System out println Main say Hello World 執(zhí)行這個程序 main 方法第一行產(chǎn)生了一個線程對象 但并沒有線程啟動 main 方法第二行產(chǎn)生了一個線程對象 并啟動了一個線程 main 方法第三行 產(chǎn)生并啟動一個線程后 主線程自己也繼續(xù)執(zhí)行其它語 句 我們先不研究 Thread 對象的具體內(nèi)容 稍微來回想一下上面的兩個概念 線程對象線程對象和線程線程 在 JAVA 中 線程對象是 JVM 產(chǎn)生的一個普通的 Object 子類 而線程是 CPU 分配給這個對象的一個運(yùn)行過程 我們說的這個線程在干什么 不是說一個線程對象在干什么 而是這個運(yùn)行過程在干什么 如果一時想不明 白 不要急 但你要記得它們不是一回事就行了 累了吧 為不么不繼續(xù)了 基于這種風(fēng)格來介紹多線程 并不是每個人都喜歡和接受的 如果你不喜 歡 正好不浪費(fèi)你的時間了 而如果你接受的話 那就看下一節(jié)吧 基于基礎(chǔ)篇 二 基于基礎(chǔ)篇 二 在進(jìn)入 java 平臺的線程對象之前 基于基礎(chǔ)篇 一 的一些問題 我先插入兩 個基本概念 線程的并發(fā)與并行線程的并發(fā)與并行 在單 CPU 系統(tǒng)中 系統(tǒng)調(diào)度在某一時刻只能讓一個線程運(yùn)行 雖然這種調(diào) 試機(jī)制有多種形式 大多數(shù)是時間片輪巡為主 但無論如何 要通過不斷切換 需要運(yùn)行的線程讓其運(yùn)行的方式就叫并發(fā)并發(fā) concurrent concurrent 而在多 CPU 系統(tǒng)中 可以讓兩個以上的線程同時運(yùn)行 這種可以同時讓兩個以上線程同時運(yùn)行的方 式叫做并行并行 parallel parallel 在上面包括以后的所有論述中 請各位朋友諒解 我無法用最準(zhǔn)確的詞語 來定義儲如并發(fā)和并行這類術(shù)語 但我以我的經(jīng)驗(yàn)?zāi)芡ㄋ椎馗嬖V大家它是怎么 一回事 如果您看到我說的一些 標(biāo)準(zhǔn) 文檔上說的不一樣 只要意思一致 那 您就不要挑刺了 JAVA JAVA 線程對象線程對象 現(xiàn)在我們來開始考察 JAVA 中線程對象 在 JAVA 中 要開始一個線程 有兩種方式 一是直接調(diào)用 Thread 實(shí)例的 start 方法 二是 將 Runable 實(shí)例傳給一個 Thread 實(shí)例然后調(diào)用它的 start 方法 在前面已經(jīng)說過 線程對象和線程是兩個完全不同的概念 這里我們再次 深入一下 生成一個線程的實(shí)例 并不代表啟動了線程 而啟動線程是說在某 個線程對象上啟動了該實(shí)例對應(yīng)的線程 當(dāng)該線程結(jié)束后 并不會就立即消失 對于從很多書籍上可以看到的基礎(chǔ)知識我就不用多說了 既然是基礎(chǔ)知識 我也著重于從普通文檔上讀不到的內(nèi)容 所以本節(jié)我重點(diǎn)要說的是兩種線程對 象產(chǎn)生線程方式的區(qū)別 class MyThread extends Thread public int x 0 public void run for int i 0 i 100 i try Thread sleep 10 catch Exception e System out println x 如果我們生成 MyThread 的一個實(shí)例 然后調(diào)用它的 start 方法 那么就 產(chǎn)生了這個實(shí)例對應(yīng)的線程 public class Test public static void main String args throws Exception MyThread mt new MyThread mt start 不用說 最終會打印出 0 到 99 現(xiàn)在我們稍微玩一點(diǎn)花樣 public class Test public static void main String args throws Exception MyThread mt new MyThread mt start System out println 101 也不用說 在基礎(chǔ)篇 一 中我們知道由于單 CPU 的原因 一般會先打印 101 然后打印 0 到 99 不過我們可以控制線程讓它按我們的意思來運(yùn)行 public class Test public static void main String args throws Exception MyThread mt new MyThread mt start mt join System out println 101 好了 我們終于看到 mt 實(shí)例對應(yīng)的線程 假如我有時說 mt 線程請你不要 怪我 不過我盡量不這么說 在運(yùn)行完成后 主線程才打印 101 因?yàn)?我們 讓當(dāng)前線程 這里是主線程 等待 mt 線程的運(yùn)行結(jié)束 在線程對象 a 上調(diào)用 join 方法 就是讓當(dāng)前正在執(zhí)行的線程等待線程對象 a 對應(yīng)的線程運(yùn)行 完成 后才繼續(xù)運(yùn)行 請大家一定要深刻理解并熟記這句話 而我這里引出這個知 識點(diǎn)的目的是為了讓你繼續(xù)看下面的例子 public class Test public static void main String args throws Exception MyThread mt new MyThread mt start mt join Thread sleep 3000 mt start 當(dāng)線程對象 mt 運(yùn)行完成后 我們讓主線程休息一下 然后我們再次在這個 線程對象上啟動線程 結(jié)果我們看到 Exception in thread main java lang IllegalThreadStateException 也就是這種線程對象一時運(yùn)行一次完成后 它就再也不能運(yùn)行第二次了 我們可以看一下它有具體實(shí)現(xiàn) public synchronized void start if started throw new IllegalThreadStateException started true group add this start0 一個 Thread 的實(shí)例一旦調(diào)用 start 方法 這個實(shí)例的 started 標(biāo)記就標(biāo) 記為 true 事實(shí)中不管這個線程后來有沒有執(zhí)行到底 只要調(diào)用了一次 start 就 再也沒有機(jī)會運(yùn)行了 這意味著 通過通過 ThreadThread 實(shí)例的實(shí)例的 start start 一個 一個 ThreadThread 的實(shí)例只能產(chǎn)生一個線程的實(shí)例只能產(chǎn)生一個線程 那么如果要在一個實(shí)例上產(chǎn)生多個線程 也就是我們常說的線程池 我們 應(yīng)該如何做呢 這就是 Runnable 接口給我們帶來的偉大的功能 class R implements Runnable private int x 0 public void run for int i 0 i 100 i try Thread sleep 10 catch Exception e System out println x 正如它的名字一樣 Runnable 的實(shí)例是可運(yùn)行的 但它自己并不能直接運(yùn)行 它需要被 Thread 對象來包裝才行運(yùn)行 public class Test public static void main String args throws Exception new Thread new R start 當(dāng)然這個結(jié)果和 mt start 沒有什么區(qū)別 但如果我們把一個 Runnable 實(shí)例給 Thread 對象多次包裝 我們就可以看到它們實(shí)際是在同一實(shí)例上啟動線 程 public class Test public static void main String args throws Exception R r new R for int i 0 i 10 i new Thread r start x 是實(shí)例對象 但結(jié)果是 x 被加到了 999 說明這 10 個線程是在同一個 r 對象上運(yùn)行的 請大家注意 因?yàn)檫@個例子是在單 CPU 上運(yùn)行的 所以沒 有對 多個線程同時操作共同的對象進(jìn)行同步 這里是為了說明的方便而簡化了同步 而真正的環(huán)境中你無法預(yù)知程序會在什么環(huán)境下運(yùn)行 所以一定要考慮同步 到這里我們做一個完整的例子來說明線程產(chǎn)生的方式不同而生成的線程的 區(qū)別 package debug import java io import java lang Thread class MyThread extends Thread public int x 0 public void run System out println x class R implements Runnable private int x 0 public void run System out println x public class Test public static void main String args throws Exception for int i 0 i 10 i Thread t new MyThread t start Thread sleep 10000 讓上面的線程運(yùn)行完成 R r new R for int i 0 i 10 i Thread t new Thread r t start 上面 10 個線程對象產(chǎn)生的 10 個線程運(yùn)行時打印了 10 次 1 下面 10 個線 程對象產(chǎn)生的 10 個線程運(yùn)行時打印了 1 到 10 我們把下面的 10 個線程稱為同同 一實(shí)例一實(shí)例 Runnable Runnable 實(shí)例實(shí)例 的多個線程的多個線程 下節(jié)我們將研究線程對象方法 還是那句話 一般文檔中可以讀到的內(nèi)容 我不會介紹太多請大家自己了解 基于基礎(chǔ)篇 三 基于基礎(chǔ)篇 三 線程對象的幾個重要的方法 盡管線程對象的常用方法可以通過 API 文檔來了解 但是有很多方法僅僅從 API 說明是無法詳細(xì)了解的 本來打算用一節(jié)的篇幅來把線程方法中一些重要的知識說完 但這樣下來估 計(jì)要很常的篇幅 可能要用好幾節(jié)才能說把和線程方法相關(guān)的一些重要的知識說 完 首先我們接基礎(chǔ)篇 二 來說明 start 方法 一個線程對象生成后 如果要產(chǎn)生一個執(zhí)行的線程 就一定要調(diào)用它的 start 方法 在介紹這個方法時不得不同時說明 run 方法 其實(shí)線程對 象的 run 方法完全是一個接口回調(diào)方法 它是你這個線程對象要完成的具體邏輯 簡 單說你要做什么就你在 run 中完成 而如何做 什么時候做就不需要你控制 了 你只要調(diào)用 start 方法 JVM 就會管理這個線程對象讓它產(chǎn)生一個線程并注冊 到線程處理系統(tǒng)中 從表面上看 start 方法調(diào)用了 run 方法 事實(shí)上 start 方法并沒有直 接調(diào)用 run 方法 在 JDK1 5 以前 start 方法是本地方法 它如何最終調(diào)用 run 方法已經(jīng)不是 JAVA 程序員所能了解的 而在 JDK1 5 中 原來的那個本地 start 方 法被 start0 代替 另個一個純 JAVA 的 start 中調(diào)用本地方法 start0 而 在 start 方法中做了一個驗(yàn)證 就是對一個全局變量 對象變量 started 做檢 驗(yàn) 如果為 true 則 start 拋出異常 不會調(diào)用本地方法 start0 否則 先將 該變量設(shè)有 true 然后 調(diào)用 start0 從中我們可以看到這個為了控制一個線程對象只能運(yùn)行成功一次 start 方法 這是因?yàn)榫€程的運(yùn)行要獲取當(dāng)前環(huán)境 包括安全 父線程的權(quán)限 優(yōu)先級等 條件 如果一個線程對象可以運(yùn)行多次 那么定義一個 static 的線程在一個環(huán) 境中獲取相應(yīng)權(quán)限和優(yōu)先級 運(yùn)行完成后它在另一個環(huán)境中利用原來的權(quán)限和優(yōu) 先級等屬性在當(dāng)前環(huán)境中運(yùn)行 這樣就造成無法預(yù)知的結(jié)果 簡單說 來 讓一個 線程對象只能成功運(yùn)行一次 是基于對線程管理的需要 start 方法最本質(zhì)的功能是從 CPU 中申請另一個線程空間來執(zhí)行 run 方法中 的代碼 它和當(dāng)前的線程是兩條線 在相對獨(dú)立的線程空間運(yùn)行 也就是說 如果 你直接調(diào)用線程對象的 run 方法 當(dāng)然也會執(zhí)行 但那是 在當(dāng)前線程中執(zhí)行 run 方法執(zhí)行完成后繼續(xù)執(zhí)行下面的代碼 而調(diào)用 start 方法后 run 方法 的代碼會和當(dāng)前線程并發(fā) 單 CPU 或并行 多 CPU 執(zhí)行 所以請記住一句話 調(diào)用線程對象的 run 方法不會產(chǎn)生一個新的線程 雖然 可以達(dá)到相同的執(zhí)行結(jié)果 但執(zhí)行過程和執(zhí)行效率不同 線程的 interrupt 方法 interrupted 和 isInterrupted 這三個方法是關(guān)系非常密切而且又比較復(fù)雜的 雖然它們各自的功能很清楚 但 它們之間的關(guān)系有大多數(shù)人不是真正的了解 先說 interrupt 方法 它是實(shí)例方法 而它也是最奇怪的方法 在 java 語 言中 線程最初被設(shè)計(jì)為 隱晦難懂 的東西 直到現(xiàn)在它的 語義不沒有象它的名 字那樣準(zhǔn)確 大多數(shù)人以為 一個線程象調(diào)用了 interrupt 方法 那它對應(yīng)的 線程就應(yīng)該被中斷而拋出異常 事實(shí)中 當(dāng)一個線程 對象調(diào)用 interrupt 方法 它 對應(yīng)的線程并沒有被中斷 只是改變了它的中斷狀態(tài) 使當(dāng)前線程的狀態(tài)變以中斷狀態(tài) 如果沒有其它影響 線程還會自己繼續(xù)執(zhí) 行 只有當(dāng)線程執(zhí)行到 sleep wait join 等方法時 或者自己檢查中斷狀態(tài)而拋 出異常的情況下 線程才會拋出異常 如果線程對象調(diào)用 interrupt 后它對應(yīng)的線程就立即中斷 那么 interrupted 方 法就不可能執(zhí)行 因?yàn)?interrupted 方法是一個 static 方法 就是說只能在當(dāng)前線程上調(diào) 用 而如果一個線程 interrupt 后它已經(jīng)中斷了 那它又如何讓自己 interrupted 正因?yàn)橐粋€線程調(diào)用 interrupt 后只是改變了中斷狀態(tài) 它可以繼續(xù)執(zhí)行 下去 在沒有調(diào)用 sleep wait join 等法或自己拋 出異常之前 它就可以調(diào)用 interrupted 來清除中斷狀態(tài) 還會原狀 interrupted 方法會檢查當(dāng)前線程 的中斷狀態(tài) 如果為 被中斷狀態(tài) 則改變當(dāng)前線程為 非中斷狀態(tài) 并返回 true 如 果為 非中斷狀態(tài) 則返回 false 它不僅檢查當(dāng)前線程是否為中斷狀態(tài) 而且在 保證當(dāng) 前線程回來非中斷狀態(tài) 所以它叫 interrupted 是說中斷的狀態(tài)已經(jīng) 結(jié)束 到非中斷狀態(tài)了 isInterrupted 方法則僅僅檢查線 程對象對應(yīng)的線程 是否是中斷狀態(tài) 并不改變它的狀態(tài) 目前大家只能先記住這三個方法的功能 只有真正深入到多線程編程實(shí)踐中 才 會體會到它們?yōu)槭裁词菍ο蠓椒?為什么是類方法 線程到底什么時候才被中斷拋出 InterruptedException 異常 我們將在提 高篇中詳細(xì)討論 sleep join yield 方法 在現(xiàn)在的環(huán)節(jié)中 我只能先說明這些方法的作用和調(diào)用原則 至于為什么 在 基礎(chǔ)篇中無法深入 只能在提高篇中詳細(xì)說明 sleep 方法中是類方法 也就是對當(dāng)前線程而言的 程序員不能指定某個線 程去 sleep 只能是當(dāng)前線程執(zhí)行到 sleep 方法時 睡 眠指定的時間 讓其它線 程運(yùn)行 事實(shí)上也只能是類方法 在當(dāng)前線程上調(diào)用 試想如果你調(diào)用一個線程 對象的 sleep 方法 那么這個對象對應(yīng)的線程如 果不是正在運(yùn)行 它如何 sleep 所以只有當(dāng)前線程 因?yàn)樗趫?zhí)行 你才能保證它可以調(diào)用 sleep 方法 原則 在同步方法中盡量不要調(diào)用線程的 sleep 方法 或者簡單說 對于 一般水平的程序員你基本不應(yīng)該調(diào)用 sleep 方法 join 方法 正如第一節(jié)所言 在一個線程對象上調(diào)用 join 方法 是當(dāng)前線 程等待這個線程對象對應(yīng)的線程結(jié)束 比如有兩個工作 工作 A 要耗時 10 秒鐘 工作 B 要耗時 10 秒或更多 我們在程序中先生成一個線程去做工作 B 然后做 工作 A new B start 做工作 B A 做工作 A 工作 A 完成后 下面要等待工作 B 的結(jié)果來進(jìn)行處理 如果工作 B 還沒有完 成我就不能進(jìn)行下面的工作 C 所以 B b new B b start 做工作 B A 做工作 A b join 等工作 B 完成 C 繼續(xù)工作 C 原則 join 是測試其它工作狀態(tài)的唯一正確方法 我見過很多人 甚至有 的是博士生 在處理一項(xiàng)工作時如果另一項(xiàng)工作沒有完成 說讓當(dāng)前工 作線程 sleep x 我問他 你這個 x 是如何指定的 你怎么知道是 100 毫秒而不是 99 毫 秒或是 101 毫秒 其實(shí)這就是 OnXXX 事件的實(shí)質(zhì) 我們不 是要等多長時間才去做 什么事 而是當(dāng)?shù)却墓ぷ髡猛瓿傻臅r候去做 yield 方法也是類方法 只在當(dāng)前線程上調(diào)用 理由同上 它主是讓當(dāng)前線 程放棄本次分配到的時間片原則 不是非常必要的情況下 沒有理 由調(diào)用它 調(diào)用這個方法不會提高任何效率 只是降低了 CPU 的總周期上面介紹的線程一些 方法 基于 基礎(chǔ)篇 而言只能簡單提及 以后具體應(yīng)用中我會結(jié)合 實(shí)例詳細(xì)論述 線程本身的其它方法請參看 API 文檔 下一節(jié)介紹非線程的方法 但和線程 密切相關(guān)的兩 三 個對象方法 wait notify notifyAll 這是在多線程中非常重要的方法 基于基于基礎(chǔ)篇 四基礎(chǔ)篇 四 wait notify notityAll 方法方法 關(guān)于這兩個方法 有很多的內(nèi)容需要說明 在下面的說明中可能會有很多地 方不能一下子明白 但在看完本節(jié)后 即使不能完全明白 你也一定要回過頭來記 住下面的兩句話 wait notify notityAll wait notify notityAll 方法是普通對象的方法方法是普通對象的方法 Object Object 超類中實(shí)超類中實(shí) 現(xiàn)現(xiàn) 而不是線程對象的方法而不是線程對象的方法 wait notify notityAll wait notify notityAll 方法只能在同步方法中調(diào)用方法只能在同步方法中調(diào)用 線程的互斥控制線程的互斥控制 多個線程同時操作某一對象時 一個線程對該對象的操作可能會改變其狀態(tài) 而 該狀態(tài)會影響另一線程對該對象的真正結(jié)果 這個例子我們在太多的文檔中可以看到 就象兩個操售票員同時售出同一張 票一樣 線程線程 A線程線程 B 1 線程 A 在數(shù)據(jù)庫中查詢存票 發(fā)現(xiàn)票 C 可以 賣出 class left 2 線程 A 接受用戶訂票請求 準(zhǔn)備出 票 3 這時切換到了線程 B 執(zhí)行 4 線程 B 在數(shù)據(jù)庫中查詢存票 發(fā)現(xiàn)票 C 可以 賣出 5 線程 B 將票賣了出去 6 切換到線程 A 執(zhí)行 線程 A 賣了一張已經(jīng)賣 出的票 所以需要一種機(jī)制來管理這類問題的發(fā)生 當(dāng)某個線程正在執(zhí)行一個不可分 割的部分時 其它線程不能不能同時執(zhí)行這一部分 象這種控制某一時刻只能有一個線程執(zhí)行某個執(zhí)行單元的機(jī)制就叫互斥控 制或共享互斥 mutual exclusion 在 JAVA 中 用 synchornized 關(guān)鍵字來實(shí)現(xiàn)互斥控制 暫時這樣認(rèn)為 JDK1 5 已經(jīng)發(fā)展了新的機(jī)制 synchornized 關(guān)鍵字關(guān)鍵字 把一個單元聲明為 synchornized 就可以讓在同一時間只有一個線程操作 該方法 有人說 synchornized 就是一把鎖 事實(shí)上它確實(shí)存在鎖 但是是誰的鎖 鎖 誰 這是一個非常復(fù)雜的問題 每個對象只有一把監(jiān)視鎖 monitor lock 一次只能被一個線程獲取 當(dāng)一 個線程獲取了這一個鎖后 其它線程就只能等待這個線程釋放鎖才能再獲取 那么 synchornized 關(guān)鍵字到底鎖什么 得到了誰的鎖 對于同步塊 synchornized 獲取的是參數(shù)中的對象鎖 synchornized obj 線程執(zhí)行到這里時 首先要獲取 obj 這個實(shí)例的鎖 如果沒有獲取到線程只 能等待 如果多個線程執(zhí)行到這里 只能有一個線程獲取 obj 的鎖 然后執(zhí)行 中 的語句 所以 obj 對象的作用范圍不同 控制程序不同 假如 public void test Object o new Object synchornized obj 這段程序控制不了任何 多個線程之間執(zhí)行到 Object o new Object 時會各自產(chǎn)生一個對象然后獲取這個對象有監(jiān)視鎖 各自皆大歡喜地執(zhí)行 而如果是類的屬性 class Test Object o new Object public void test synchornized o 所有執(zhí)行到 Test 實(shí)例的 synchornized o 的線程 只有一個線程可以獲取 到監(jiān)視鎖 有時我們會這樣 public void test synchornized this 那么所有執(zhí)行 Test 實(shí)例的線程只能有一個線程執(zhí)行 而 synchornized o 和 synchornized this 的范圍是不同 的 因?yàn)閳?zhí)行到 Test 實(shí)例的 synchornized o 的線程等待時 其它線程可以執(zhí)行 Test 實(shí)例的 synchornized o1 部分 但多 個線程同時只有一個可以執(zhí)行 Test 實(shí)例的 synchornized this 而對于 synchornized Test class 這樣的同步塊而言 所有調(diào)用 Test 多個實(shí)例的線程賜教只能有一個線程可 以執(zhí)行 synchornized 方法方法 如果一個方法聲明為 synchornized 的 則等同于把在為個方法上調(diào)用 synchornized this 如果一個靜態(tài)方法被聲明為 synchornized 則等同于把在為個方法上調(diào)用 synchornized 類 class 現(xiàn)在進(jìn)入 wait 方法和 notify notifyAll 方法 這兩個 或叫三個 方法都是 Object 對象的方法 而不是線程對象的方法 如同鎖一樣 它們是在線程中調(diào)用 某一對象上執(zhí)行的 class Test public synchornized void test 獲取條件 int x 要求大于 100 if x 100 wait 這里為了說明方法沒有加在 try catch 中 如果沒有明確在哪個對象 上調(diào)用 wait 方法 則為 this wait 假如 Test t new Test 現(xiàn)在有兩個線程都執(zhí)行到 t test 方法 其中線程 A 獲取了 t 的對象鎖 進(jìn)入 test 方法內(nèi) 這時 x 小于 100 所以線程 A 進(jìn)入等待 當(dāng)一個線程調(diào)用了 wait 方法后 這個線程就進(jìn)入了這個對象的休息室 waitset 這是一個虛擬的對象 但 JVM 中一定存在這樣的一個數(shù)據(jù)結(jié)構(gòu)用來記 錄當(dāng)前對象中有哪些程線程在等待 當(dāng)一個線程進(jìn)入等待時 它就會釋放鎖 讓其它線程來獲取這個鎖 所以線程 B 有機(jī)會獲得了線程 A 釋放的鎖 進(jìn)入 test 方法 如果這時 x 還 是小于 100 線程 B 也進(jìn)入了 t 的休息室 這兩個線程只能等待其它線程調(diào)用 notity All 來喚醒 但是如果調(diào)用的是有參數(shù)的 wait time 方法 則線程 A B 都會在休息室中 等待這個時間后自動喚醒 為什么真正的應(yīng)用都是用為什么真正的應(yīng)用都是用 while 條件條件 而不用而不用 if 條件條件 在實(shí)際的編程中我們看到大量的例子都是用 while x 100 wait go 而不是用 if 為什么呢 在多個線程同時執(zhí)行時 if x this maxSize try this wait catch Exception e this add f notifyAll 拿菜拿菜 同上面 如果桌子上一盤菜也沒有 所有食客都要等待 public synchronized Food getFood while this size this maxSize try this wait catch Exception e this add f notifyAll public synchronized Food getFood while this size t maxSize try t wait catch Exception e t add f t notifyAll 2 同步的范圍 在本例中是放和取兩個方法 不能把做菜和吃菜這種各自 不相干的工作放在受保護(hù)的范圍中 3 參與者與容積比 對于生產(chǎn)者和消費(fèi)者的比例 以及桌子所能放置最多菜的數(shù)量三者之間的 關(guān)系是影響性能的重要因素 如果是過多的生產(chǎn)者在等待 則要增加消費(fèi)者或 減少生產(chǎn)者的數(shù)據(jù) 反之則增加生產(chǎn)者或減少消費(fèi)者的數(shù)量 另外如果桌子有足夠的容量可以很大程序提升性能 這種情況下可以同時 提高生產(chǎn)者和消費(fèi)者的數(shù)量 但足夠大的容時往往你要有足夠大的物理內(nèi)存 本節(jié)繼續(xù)上一節(jié)的討論 一個線程在進(jìn)入對象的休息室一個線程在進(jìn)入對象的休息室 調(diào)用該對象的調(diào)用該對象的 wait wait 方法方法 后會釋放對該后會釋放對該 對象的鎖對象的鎖 基于這個原因 在同步中 除非必要 否則你不應(yīng)用使用 Thread sleep long l 方法 因?yàn)?sleep 方法并不釋放對象的鎖 這是一個極其惡劣的品德 你自己什么事也不干 進(jìn)入 sleep 狀態(tài) 卻抓 住競爭對象的監(jiān)視鎖不讓其它需要該對象監(jiān)視鎖的線程運(yùn)行 簡單說是極端自 私的一種行為 但我看到過很多程序員仍然有在同步方法中調(diào)用 sleep 的代碼 看下面的例子 package debug class SleepTest public synchronized void wantSleep try Thread sleep 1000 60 catch Exception e System out println 111 public synchronized void say System out println 123 class T1 extends Thread SleepTest st public T1 SleepTest st this st st public void run st wantSleep class T2 extends Thread SleepTest st public T2 SleepTest st this st st public void run st say public class Test public static void main String args throws Exception SleepTest st new SleepTest new T1 st start new T2 st start 我們看到 線程 T1 的實(shí)例運(yùn)行后 當(dāng)前線程抓住了 st 實(shí)例的鎖 然后進(jìn) 入了 sleep 直到它睡滿 60 秒后才運(yùn)行到 System out println 111 然后 run 方法運(yùn)行完成釋放了對 st 的監(jiān)視鎖 線程 T2 的實(shí)例才得到運(yùn)行的機(jī)會 而如果我們把 wantSleep 方法改成 public synchronized void wantSleep try Thread sleep 1000 60 this wait 1000 60 catch Exception e System out println 111 我們看到 T2 的實(shí)例所在的線程立即就得到了運(yùn)行機(jī)會 首先打印了 123 而 T1 的實(shí)例所在的線程仍然等待 直到等待 60 秒后運(yùn)行到 System out println 111 方法 所以 調(diào)用 wait long l 方法不僅達(dá)到了阻塞當(dāng)前線程規(guī)定時間內(nèi)不運(yùn)行 而且讓其它有競爭需求的線程有了運(yùn)行機(jī)會 這種利人不損己的方法 何樂而 不為 這也是一個有良心的程序員應(yīng)該遵循的原則 當(dāng)一個線程調(diào)用 wait long l 方法后 線程如果繼續(xù)運(yùn)行 你無法知道它 是等待時間完成了還是在 wait 時被其它線程喚醒了 如果你非常在意它一定要 等待足夠的時間才執(zhí)行某任務(wù) 而不希望是中途被喚醒 這里有一個不是非常 準(zhǔn)確的方法 long l System System currentTimeMillis wait 1000 準(zhǔn)備讓當(dāng)前線 程等待 1 秒 while System System currentTimeMillis l 1000 執(zhí) 行到這里說明它還沒有等待到 1 秒 是讓其它線程給鬧醒了 wait 1000 System System currentTimeMillis l 繼續(xù)等待余下的時間 這種方法不是很準(zhǔn)確 但基本上能達(dá)到目的 所以在同步方法中 除非你明確知道自己在干什么 非要這么做的話 你 沒有理由使用 sleep wait 方法足夠達(dá)到你想要的目的 而如果你是一 個很保 守的人 看到上面這段話后 你對 sleep 方法深惡痛絕 堅(jiān)決不用 sleep 了 那么在非同步的方法中 沒有和其它線程競爭的對象 你想讓當(dāng)前線 程阻塞一 定時間后再運(yùn)行 應(yīng)該如何做呢 這完全是一種賣弄 在非同步的方法中你就 應(yīng)該合理地應(yīng)用 sleep 嘛 但如果你堅(jiān)決不用 sleep 那就這樣來 做吧 public static mySleep long l Object o new Object synchronized o try o wait l catch Exception e 放心吧 沒有人能在這個方法外調(diào)用 o notify All 所以 o wait l 會一 直等到設(shè)定的時間才會運(yùn)行完成 虛擬鎖的使用虛擬鎖的使用 虛擬鎖簡單說就是不要調(diào)用 synchronized 方法 它等同于 synchronized this 和不要調(diào)用 synchronized this 這樣所有調(diào)用在這個 實(shí)例上的所有同步方法的線程只能有一個線程可以運(yùn)行 也就是說 如果一個類有兩個同步方法 m1 m2 那么不僅是兩個以上線調(diào)用 m1 方法 的線程只有一個能運(yùn)行 就是兩個分別調(diào)用 m1 m2 的線程也只有一個能運(yùn)行 當(dāng)然非同步方法不存在任何競爭 在一個線程獲取該對象的監(jiān)視鎖后這個對象 的非同步方法可以被任何線程調(diào)用 而大多數(shù)時候 我們可能會出現(xiàn)這種情況 多個線程調(diào)用 m1 時需要保護(hù)一 種資源 而多個線程調(diào)用 M2 時要保護(hù)的是另一種資源 如果我們把 m1 m2 都 設(shè)成同步方法 兩

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論