熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

java 多線程 完全版

2022-06-13   來源: Java高級技術 

  盡管線程對象的常用方法可以通過API文檔來了解但是有很多方法僅僅從API說明是無法詳細了解的我們先來說一下線程對象的幾個重要的方法
    首先我們來說明start()方法
    一個線程對象生成後如果要產生一個執行的線程就一定要調用它的start()方法在介紹這個方法時不得不同時說明run方法其實線程對 象的run方法完全是一個接口回調方法它是你這個線程對象要完成的具體邏輯簡單說你要做什麼就你在run中完成而如何做什麼時候做就不需要你控制 了你只要調用start()方法JVM就會管理這個線程對象讓它產生一個線程並注冊到線程處理系統中
    從表面上看start()方法調用了run()方法事實上start()方法並沒有直接調用run方法在JDK以前 start()方法是本地方法它如何最終調用run方法已經不是JAVA程序員所能了解的而在JDK原來的那個本地start()方法被 start()代替另個一個純JAVA的start()中調用本地方法start()而在start()方法中做了一個驗證就是對一個全局變量 (對象變量)started做檢驗如果為true則start()拋出異常不會調用本地方法start()否則先將該變量設有true然後 調用start()
    從中我們可以看到這個為了控制一個線程對象只能運行成功一次start()方法這是因為線程的運行要獲取當前環境包括安全父線程的權限 優先級等條件如果一個線程對象可以運行多次那麼定義一個static 的線程在一個環境中獲取相應權限和優先級運行完成後它在另一個環境中利用原來的權限和優先級等屬性在當前環境中運行這樣就造成無法預知的結果簡單說 來讓一個線程對象只能成功運行一次是基於對線程管理的需要
    start()方法最本質的功能是從CPU中申請另一個線程空間來執行 run()方法中的代碼它和當前的線程是兩條線在相對獨立的線程空間運行也就是說如果你直接調用線程對象的run()方法當然也會執行但那是 在當前線程中執行run()方法執行完成後繼續執行下面的代碼而調用start()方法後run()方法的代碼會和當前線程並發(單CPU)或並行 (多CPU)執行
    所以請記住一句話[調用線程對象的run方法不會產生一個新的線程]雖然可以達到相同的執行結果但執行過程和執行效率不同
    [線程的interrupt()方法interrupted()和isInterrupted()]
    這三個方法是關系非常密切而且又比較復雜的雖然它們各自的功能很清楚但它們之間的關系有大多數人不是真正的了解
    先說interrupt()方法它是實例方法而它也是最奇怪的方法在java語言中線程最初被設計為隱晦難懂的東西直到現在它的 語義不沒有象它的名字那樣准確大多數人以為一個線程象調用了interrupt()方法那它對應的線程就應該被中斷而拋出異常事實中當一個線程 對象調用interrupt()方法它對應的線程並沒有被中斷只是改變了它的中斷狀態
    使當前線程的狀態變以中斷狀態如果沒有其它影響線程還會自己繼續執行
    只有當線程執行到sleepwaitjoin等方法時或者自己檢查中斷狀態而拋出異常的情況下線程才會拋出異常
    如果線程對象調用interrupt()後它對應的線程就立即中斷那麼interrupted()方法就不可能執行
    因為interrupted()方法是一個static方法就是說只能在當前線程上調用而如果一個線程interrupt()後它已經中斷了那它又如何讓自己interrupted()?
    正因為一個線程調用interrupt()後只是改變了中斷狀態它可以繼續執行下去在沒有調用sleepwaitjoin等法或自己拋 出異常之前它就可以調用interrupted()來清除中斷狀態(還會原狀)interrupted()方法會檢查當前線程的中斷狀態如果為 被中斷狀態則改變當前線程為非中斷狀態並返回true如果為非中斷狀態則返回false它不僅檢查當前線程是否為中斷狀態而且在保證當 前線程回來非中斷狀態所以它叫interrupted是說中斷的狀態已經結束(到非中斷狀態了)isInterrupted()方法則僅僅檢查線 程對象對應的線程是否是中斷狀態並不改變它的狀態
    目前大家只能先記住這三個方法的功能只有真正深入到多線程編程實踐中才會體會到它們為什麼是對象方法為什麼是類方法
    線程到底什麼時候才被中斷拋出InterruptedException異常我們將在提高篇中詳細討論


    [sleep()join()yield()方法]
    在現在的環節中我只能先說明這些方法的作用和調用原則至於為什麼在基礎篇中無法深入只能在提高篇中詳細說明
    sleep()方法中是類方法也就是對當前線程而言的程序員不能指定某個線程去sleep只能是當前線程執行到sleep()方法時睡 眠指定的時間(讓其它線程運行)事實上也只能是類方法在當前線程上調用試想如果你調用一個線程對象的sleep()方法那麼這個對象對應的線程如 果不是正在運行它如何sleep()?所以只有當前線程因為它正在執行你才能保證它可以調用sleep()方法
    原則:[在同步方法中盡量不要調用線程的sleep()方法]或者簡單說對於一般水平的程序員你基本不應該調用sleep()方法
    join()方法正如第一節所言在一個線程對象上調用join方法是當前線程等待這個線程對象對應的線程結束比如有兩個工作工作A要耗時秒鐘工作B要耗時秒或更多我們在程序中先生成一個線程去做工作B然後做工作A
    new?B()start();//做工作B
    A();//做工作A
    工作A完成後下面要等待工作B的結果來進行處理如果工作B還沒有完成我就不能進行下面的工作C所以
    B?b?=?new?B();
    bstart();//做工作B
    A();//做工作A
    bjoin();//等工作B完成
    C();//繼續工作C
    原則:[join是測試其它工作狀態的唯一正確方法]我見過很多人甚至有的是博士生在處理一項工作時如果另一項工作沒有完成說讓當前工 作線程sleep(x)我問他你這個x是如何指定的你怎麼知道是毫秒而不是毫秒或是毫秒?其實這就是OnXXX事件的實質我們不 是要等多長時間才去做什麼事而是當等待的工作正好完成的時候去做
    yield()方法也是類方法只在當前線程上調用理由同上它主是讓當前線程放棄本次分配到的時間片原則:[不是非常必要的情況下沒有理 由調用它]調用這個方法不會提高任何效率只是降低了CPU的總周期上面介紹的線程一些方法基於(基礎篇)而言只能簡單提及以後具體應用中我會結合 實例詳細論述
    線程本身的其它方法請參看API文檔下一節介紹非線程的方法但和線程密切相關的兩[三]個對象方法:
    [wait()notify()/notifyAll()]
    這是在多線程中非常重要的方法
    關於這兩個方法有很多的內容需要說明在下面的說明中可能會有很多地方不能一下子明白但在看完本節後即使不能完全明白你也一定要回過頭來記住下面的兩句話:
    [wait()notify()/notityAll()方法是普通對象的方法(Object超類中實現)而不是線程對象的方法]
    [wait()notify()/notityAll()方法只能在同步方法中調用]
    [線程的互斥控制]
    多個線程同時操作某一對象時一個線程對該對象的操作可能會改變其狀態而該狀態會影響另一線程對該對象的真正結果
    這個例子我們在太多的文檔中可以看到就象兩個操售票員同時售出同一張票一樣
    線程A 線程B
    線程A在數據庫中查詢存票發現票C可以賣出
    class=left線程A接受用戶訂票請求准備出票
    這時切換到了線程B執行
    線程B在數據庫中查詢存票發現票C可以賣出
    線程B將票賣了出去
    切換到線程A執行線程A賣了一張已經賣出的票
    所以需要一種機制來管理這類問題的發生當某個線程正在執行一個不可分割的部分時其它線程不能不能同時執行這一部分
    象這種控制某一時刻只能有一個線程執行某個執行單元的機制就叫互斥控制或共享互斥(mutual exclusion)
    在JAVA中用synchornized關鍵字來實現互斥控制(暫時這樣認為JDK已經發展了新的機制)
    [synchornized關鍵字]
    把一個單元聲明為synchornized就可以讓在同一時間只有一個線程操作該方法
    有人說synchornized就是一把鎖事實上它確實存在鎖但是是誰的鎖鎖誰這是一個非常復雜的問題
    每個對象只有一把監視鎖(monitor lock)一次只能被一個線程獲取當一個線程獲取了這一個鎖後其它線程就只能等待這個線程釋放鎖才能再獲取
    那麼synchornized關鍵字到底鎖什麼?得到了誰的鎖?
    對於同步塊synchornized獲取的是參數中的對象鎖:
    synchornized(obj){  //  }  線程執行到這裡時首先要獲取obj這個實例的鎖如果沒有獲取到線程只能等待如果多個線程執行到這裡只能有一個線程獲取obj的鎖然後執行{}中的語句所以obj對象的作用范圍不同控制程序不同
    假如:
    public void test(){  Object o = new Object();  synchornized(obj){  //  }  }  這段程序控制不了任何多個線程之間執行到Object o = new Object();時會各自產生一個對象然後獲取這個對象有監視鎖各自皆大歡喜地執行
    而如果是類的屬性:
    class Test{    Object o = new Object();    public void test(){    synchornized(o){    //    }    }}  所有執行到Test實例的synchornized(o)的線程只有一個線程可以獲取到監視鎖
    有時我們會這樣:
    public void test(){    synchornized(this){    //    }    }  那麼所有執行Test實例的線程只能有一個線程執行而synchornized(o)和synchornized(this)的范圍是不同 的因為執行到Test實例的synchornized(o)的線程等待時其它線程可以執行Test實例的synchornized(o)部分但多 個線程同時只有一個可以執行Test實例的synchornized(this)]
    而對於
    synchornized(Testclass){    //    }  這樣的同步塊而言所有調用Test多個實例的線程賜教只能有一個線程可以執行
    [synchornized方法]
    如果一個方法聲明為synchornized的則等同於把在為個方法上調用synchornized(this)
    如果一個靜態方法被聲明為synchornized則等同於把在為個方法上調用synchornized(類class)
    現在進入wait方法和notify/notifyAll方法這兩個(或叫三個)方法都是Object對象的方法而不是線程對象的方法如同鎖一樣它們是在線程中調用某一對象上執行的
    class Test{    public synchornized void test(){    //獲取條件int x 要求大於;    if(x < )    wait();    }    }  這裡為了說明方法沒有加在try{}catch(){}中如果沒有明確在哪個對象上調用wait()方法則為thiswait();
    假如:
    Test t = new Test();
    現在有兩個線程都執行到ttest();方法其中線程A獲取了t的對象鎖進入test()方法內
    這時x小於所以線程A進入等待
    當一個線程調用了wait方法後這個線程就進入了這個對象的休息室(waitset)這是一個虛擬的對象但JVM中一定存在這樣的一個數據結構用來記錄當前對象中有哪些程線程在等待
    當一個線程進入等待時它就會釋放鎖讓其它線程來獲取這個鎖
    所以線程B有機會獲得了線程A釋放的鎖進入test()方法如果這時x還是小於線程B也進入了t的休息室
    這兩個線程只能等待其它線程調用notity[All]來喚醒
    但是如果調用的是有參數的wait(time)方法則線程AB都會在休息室中等待這個時間後自動喚醒
    [為什麼真正的應用都是用while(條件)而不用if(條件)]
    在實際的編程中我們看到大量的例子都是用?
    while(x < )
    wait();go();而不是用if為什麼呢?
    在多個線程同時執行時if(x <)是不安全的因為如果線程A和線程B都在t的休息室中等待這時另一個線程使x==並調用notifyAll方法線程A繼續 執行下面的go()而它執行完成後x有可能又小於比如下面的程序中調用了x這時切換到線程B線程B沒有繼續判斷直接執行go(); 就產生一個錯誤的條件只有while才能保證線程B又繼續檢查一次
    [notify/notifyAll方法]
    這兩個方法都是把某個對象上休息區內的線程喚醒notify只能喚醒一個但究竟是哪一個不能確定而notifyAll則喚醒這個對象上的休息室中所有的線程
    一般有為了安全性我們在絕對多數時候應該使用notifiAll()除非你明確知道只喚醒其中的一個線程
    那麼是否是只要調用一個對象的wait()方法當前線程就進入了這個對象的休息室呢?事實中要調用一個對象的wait()方法只有當前線程獲取了這個對象的鎖換句話說一定要在這個對象的同步方法或以這個對象為參數的同步塊中
    class MyThread extends Thread{    Test t = new Test();    public void run(){    ttest();    Systemoutprintln(Thread say:HelloWorld!);    }    }     public class Test {    int x = ;    public  void test(){    if(x==)    try{    wait();    }catch(Exception e){}    }    public static void main(String[] args) throws Exception{    new MyThread()start();    }    }  這個線程就不會進入t的wait方法而直接打印出Thread say:HelloWorld!
    而如果改成:
    public class Test {      int x = ;      public synchornized void test(){      if(x==)      try{      wait();      }catch(Exception e){}      }      public static void main(String[] args) throws Exception{      new MyThread()start();      }      }


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27650.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.