<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    weidagang2046的專欄

    物格而后知致
    隨筆 - 8, 文章 - 409, 評論 - 101, 引用 - 0
    數(shù)據(jù)加載中……

    Java 線程/內(nèi)存模型的缺陷和增強

    shyguy 原創(chuàng)


    Java在語言層次上實現(xiàn)了對線程的支持。它提供了Thread/Runnable/ThreadGroup等一系列封裝的類和接口,讓程序員可以高效的開發(fā)Java多線程應用。為了實現(xiàn)同步,Java提供了synchronize關鍵字以及object的wait()/notify()機制,可是在簡單易用的背后,應藏著更為復雜的玄機,很多問題就是由此而起。

    一、Java內(nèi)存模型


        在了解Java的同步秘密之前,先來看看JMM(Java Memory Model)。
        Java被設計為跨平臺的語言,在內(nèi)存管理上,顯然也要有一個統(tǒng)一的模型。而且Java語言最大的特點就是廢除了指針,把程序員從痛苦中解脫出來,不用再考慮內(nèi)存使用和管理方面的問題。
    可惜世事總不盡如人意,雖然JMM設計上方便了程序員,但是它增加了虛擬機的復雜程度,而且還導致某些編程技巧在Java語言中失效。

        JMM主要是為了規(guī)定了線程和內(nèi)存之間的一些關系。對Java程序員來說只需負責用synchronized同步關鍵字,其它諸如與線程/內(nèi)存之間進行數(shù)據(jù)交換/同步等繁瑣工作均由虛擬機負責完成。如圖1所示:根據(jù)JMM的設計,系統(tǒng)存在一個主內(nèi)存(Main Memory),Java中所有變量都儲存在主存中,對于所有線程都是共享的。每條線程都有自己的工作內(nèi)存(Working Memory),工作內(nèi)存中保存的是主存中某些變量的拷貝,線程對所有變量的操作都是在工作內(nèi)存中進行,線程之間無法相互直接訪問,變量傳遞均需要通過主存完成。

    圖1 Java內(nèi)存模型示例圖

    線程若要對某變量進行操作,必須經(jīng)過一系列步驟:首先從主存復制/刷新數(shù)據(jù)到工作內(nèi)存,然后執(zhí)行代碼,進行引用/賦值操作,最后把變量內(nèi)容寫回Main Memory。Java語言規(guī)范(JLS)中對線程和主存互操作定義了6個行為,分別為load,save,read,write,assign和use,這些操作行為具有原子性,且相互依賴,有明確的調用先后順序。具體的描述請參見JLS第17章。

        我們在前面的章節(jié)介紹了synchronized的作用,現(xiàn)在,從JMM的角度來重新審視synchronized關鍵字。
    假設某條線程執(zhí)行一個synchronized代碼段,其間對某變量進行操作,JVM會依次執(zhí)行如下動作:
    (1) 獲取同步對象monitor (lock)
    (2) 從主存復制變量到當前工作內(nèi)存 (read and load)
    (3) 執(zhí)行代碼,改變共享變量值 (use and assign)
    (4) 用工作內(nèi)存數(shù)據(jù)刷新主存相關內(nèi)容 (store and write)
    (5) 釋放同步對象鎖 (unlock)
    可見,synchronized的另外一個作用是保證主存內(nèi)容和線程的工作內(nèi)存中的數(shù)據(jù)的一致性。如果沒有使用synchronized關鍵字,JVM不保證第2步和第4步會嚴格按照上述次序立即執(zhí)行。因為根據(jù)JLS中的規(guī)定,線程的工作內(nèi)存和主存之間的數(shù)據(jù)交換是松耦合的,什么時候需要刷新工作內(nèi)存或者更新主內(nèi)存內(nèi)容,可以由具體的虛擬機實現(xiàn)自行決定。如果多個線程同時執(zhí)行一段未經(jīng)synchronized保護的代碼段,很有可能某條線程已經(jīng)改動了變量的值,但是其他線程卻無法看到這個改動,依然在舊的變量值上進行運算,最終導致不可預料的運算結果。

    二、DCL失效


    這一節(jié)我們要討論的是一個讓Java丟臉的話題:DCL失效。在開始討論之前,先介紹一下LazyLoad,這種技巧很常用,就是指一個類包含某個成員變量,在類初始化的時候并不立即為該變量初始化一個實例,而是等到真正要使用到該變量的時候才初始化之。
    例如下面的代碼:
    代碼1
    1. class Foo {
    2.   private Resource res = null;
    3.   public Resource getResource() {
    4.     if (res == null)
    5.       res = new Resource();
    6.     return res;
    7.   }
    8. }

    由于LazyLoad可以有效的減少系統(tǒng)資源消耗,提高程序整體的性能,所以被廣泛的使用,連Java的缺省類加載器也采用這種方法來加載Java類。
    在單線程環(huán)境下,一切都相安無事,但如果把上面的代碼放到多線程環(huán)境下運行,那么就可能會出現(xiàn)問題。假設有2條線程,同時執(zhí)行到了if(res == null),那么很有可能res被初始化2次,為了避免這樣的Race Condition,得用synchronized關鍵字把上面的方法同步起來。代碼如下:
    代碼2
    1. Class Foo {
    2.   Private Resource res = null;
    3.   Public synchronized Resource getResource() {
    4.     If (res == null)
    5.       res = new Resource();
    6.     return res;
    7.   }
    8. }

    現(xiàn)在Race Condition解決了,一切都很好。

    N天過后,好學的你偶然看了一本Refactoring的魔書,深深為之打動,準備自己嘗試這重構一些以前寫過的程序,于是找到了上面這段代碼。你已經(jīng)不再是以前的Java菜鳥,深知synchronized過的方法在速度上要比未同步的方法慢上100倍,同時你也發(fā)現(xiàn),只有第一次調用該方法的時候才需要同步,而一旦res初始化完成,同步完全沒必要。所以你很快就把代碼重構成了下面的樣子:
    代碼3
    1. Class Foo {
    2. Private Resource res = null;
    3.   Public Resource getResource() {
    4.     If (res == null){
    5.       synchronized(this){
    6.         if(res == null){
    7.           res = new Resource();
    8. }
    9. }
    10.     }
    11.     return res;
    12.   }
    13. }

    這種看起來很完美的優(yōu)化技巧就是Double-Checked Locking。但是很遺憾,根據(jù)Java的語言規(guī)范,上面的代碼是不可靠的。

        造成DCL失效的原因之一是編譯器的優(yōu)化會調整代碼的次序。只要是在單個線程情況下執(zhí)行結果是正確的,就可以認為編譯器這樣的“自作主張的調整代碼次序”的行為是合法的。JLS在某些方面的規(guī)定比較自由,就是為了讓JVM有更多余地進行代碼優(yōu)化以提高執(zhí)行效率。而現(xiàn)在的CPU大多使用超流水線技術來加快代碼執(zhí)行速度,針對這樣的CPU,編譯器采取的代碼優(yōu)化的方法之一就是在調整某些代碼的次序,盡可能保證在程序執(zhí)行的時候不要讓CPU的指令流水線斷流,從而提高程序的執(zhí)行速度。正是這樣的代碼調整會導致DCL的失效。為了進一步證明這個問題,引用一下《DCL Broken Declaration》文章中的例子:
    設一行Java代碼:
    1. Objects[i].reference = new Object();

    經(jīng)過Symantec JIT編譯器編譯過以后,最終會變成如下匯編碼在機器中執(zhí)行:
    [pre]
    0206106A  mov     eax,0F97E78h
    0206106F  call      01F6B210             ;為Object申請內(nèi)存空間
                                             ; 返回值放在eax中
    02061074  mov     dword ptr [ebp],eax       ; EBP 中是objects[i].reference的地址
                                             ; 將返回的空間地址放入其中
                                             ; 此時Object尚未初始化
    02061077  mov     ecx,dword ptr [eax]       ; dereference eax所指向的內(nèi)容
                                             ; 獲得新創(chuàng)建對象的起始地址
    02061079  mov     dword ptr [ecx],100h      ; 下面4行是內(nèi)聯(lián)的構造函數(shù)
    0206107F  mov     dword ptr [ecx+4],200h    
    02061086  mov     dword ptr [ecx+8],400h
    0206108D  mov     dword ptr [ecx+0Ch],0F84030h
    [/pre]
    可見,Object構造函數(shù)尚未調用,但是已經(jīng)能夠通過objects[i].reference獲得Object對象實例的引用。
    如果把代碼放到多線程環(huán)境下運行,某線程在執(zhí)行到該行代碼的時候JVM或者操作系統(tǒng)進行了一次線程切換,其他線程顯然會發(fā)現(xiàn)msg對象已經(jīng)不為空,導致Lazy load的判斷語句if(objects[i].reference == null)不成立。線程認為對象已經(jīng)建立成功,隨之可能會使用對象的成員變量或者調用該對象實例的方法,最終導致不可預測的錯誤。

        原因之二是在共享內(nèi)存的SMP機上,每個CPU有自己的Cache和寄存器,共享同一個系統(tǒng)內(nèi)存。所以CPU可能會動態(tài)調整指令的執(zhí)行次序,以更好的進行并行運算并且把運算結果與主內(nèi)存同步。這樣的代碼次序調整也可能導致DCL失效。回想一下前面對Java內(nèi)存模型的介紹,我們這里可以把Main Memory看作系統(tǒng)的物理內(nèi)存,把Thread Working Memory認為是CPU內(nèi)部的Cache和寄存器,沒有synchronized的保護,Cache和寄存器的內(nèi)容就不會及時和主內(nèi)存的內(nèi)容同步,從而導致一條線程無法看到另一條線程對一些變量的改動。
    結合代碼3來舉例說明,假設Resource類的實現(xiàn)如下:
    1. Class Resource{
    2.   Object obj;
    3. }

    即Resource類有一個obj成員變量引用了Object的一個實例。假設2條線程在運行,其狀態(tài)用如下簡化圖表示:

    圖2 
    現(xiàn)在Thread-1構造了Resource實例,初始化過程中改動了obj的一些內(nèi)容。退出同步代碼段后,因為采取了同步機制,Thread-1所做的改動都會反映到主存中。接下來Thread-2獲得了新的Resource實例變量res,由于沒有使用synchronized保護所以Thread-2不會進行刷新工作內(nèi)存的操作。假如之前Thread-2的工作內(nèi)存中已經(jīng)有了obj實例的一份拷貝,那么Thread-2在對obj執(zhí)行use操作的時候就不會去執(zhí)行l(wèi)oad操作,這樣一來就無法看到Thread-1對obj的改變,這顯然會導致錯誤的運算結果。此外,Thread-1在退出同步代碼段的時刻對ref和obj執(zhí)行的寫入主存的操作次序也是不確定的,所以即使Thread-2對obj執(zhí)行了load操作,也有可能只讀到obj的初試狀態(tài)的數(shù)據(jù)。(注:這里的load/use均指JMM定義的操作)

        有很多人不死心,試圖想出了很多精妙的辦法來解決這個問題,但最終都失敗了。事實上,無論是目前的JMM還是已經(jīng)作為JSR提交的JMM模型的增強,DCL都不能正常使用。在William Pugh的論文《Fixing the Java Memory Model》中詳細的探討了JMM的一些硬傷,更嘗試給出一個新的內(nèi)存模型,有興趣深入研究的讀者可以參見文后的參考資料。

        如果你設計的對象在程序中只有一個實例,即singleton的,有一種可行的解決辦法來實現(xiàn)其LazyLoad:就是利用類加載器的LazyLoad特性。代碼如下:
    1. Class ResSingleton {
    2. public static Resource res = new Resource();
    3. }

    這里ResSingleton只有一個靜態(tài)成員變量。當?shù)谝淮问褂肦esSingleton.res的時候,JVM才會初始化一個Resource實例,并且JVM會保證初始化的結果及時寫入主存,能讓其他線程看到,這樣就成功的實現(xiàn)了LazyLoad。
    除了這個辦法以外,還可以使用ThreadLocal來實現(xiàn)DCL的方法,但是由于ThreadLocal的實現(xiàn)效率比較低,所以這種解決辦法會有較大的性能損失,有興趣的讀者可以參考文后的參考資料。

        最后要說明的是,對于DCL是否有效,個人認為更多的是一種帶有學究氣的推斷和討論。而從純理論的角度來看,存取任何可能共享的變量(對象引用)都需要同步保護,否則都有可能出錯,但是處處用synchronized又會增加死鎖的發(fā)生幾率,苦命的程序員怎么來解決這個矛盾呢?事實上,在很多Java開源項目(比如Ofbiz/Jive等)的代碼中都能找到使用DCL的證據(jù),我在具體的實踐中也沒有碰到過因DCL而發(fā)生的程序異常。個人的偏好是:不妨先大膽使用DCL,等出現(xiàn)問題再用synchronized逐步排除之。也許有人偏于保守,認為穩(wěn)定壓倒一切,那就不妨先用synchronized同步起來,我想這是一個見仁見智的問題,而且得針對具體的項目具體分析后才能決定。還有一個辦法就是寫一個測試案例來測試一下系統(tǒng)是否存在DCL現(xiàn)象,附帶的光盤中提供了這樣一個例子,感興趣的讀者可以自行編譯測試。不管結果怎樣,這樣的討論有助于我們更好的認識JMM,養(yǎng)成用多線程的思路去分析問題的習慣,提高我們的程序設計能力。

    三、Java線程同步增強包


    相信你已經(jīng)了解了Java用于同步的3板斧:synchronized/wait/notify,它們的確簡單而有效。但是在某些情況下,我們需要更加復雜的同步工具。有些簡單的同步工具類,諸如ThreadBarrier,Semaphore,ReadWriteLock等,可以自己編程實現(xiàn)。現(xiàn)在要介紹的是牛人Doug Lea的Concurrent包。這個包專門為實現(xiàn)Java高級并行程序所開發(fā),可以滿足我們絕大部分的要求。更令人興奮的是,這個包公開源代碼,可自由下載。且在JDK1.5中該包將作為SDK一部分提供給Java開發(fā)人員。

    Concurrent Package提供了一系列基本的操作接口,包括sync,channel,executor,barrier,callable等。這里將對前三種接口及其部分派生類進行簡單的介紹。

        sync接口:專門負責同步操作,用于替代Java提供的synchronized關鍵字,以實現(xiàn)更加靈活的代碼同步。其類關系圖如下:

    圖3 Concurrent包Sync接口類關系圖
    Semaphore:和前面介紹的代碼類似,可用于pool類實現(xiàn)資源管理限制。提供了acquire()方法允許在設定時間內(nèi)嘗試鎖定信號量,若超時則返回false。

    Mutex:和Java的synchronized類似,與之不同的是,synchronized的同步段只能限制在一個方法內(nèi),而Mutex對象可以作為參數(shù)在方法間傳遞,所以可以把同步代碼范圍擴大到跨方法甚至跨對象。

    NullSync:一個比較奇怪的東西,其方法的內(nèi)部實現(xiàn)都是空的,可能是作者認為如果你在實際中發(fā)現(xiàn)某段代碼根本可以不用同步,但是又不想過多改動這段代碼,那么就可以用NullSync來替代原來的Sync實例。此外,由于NullSync的方法都是synchronized,所以還是保留了“內(nèi)存壁壘”的特性。

    ObservableSync:把sync和observer模式結合起來,當sync的方法被調用時,把消息通知給訂閱者,可用于同步性能調試。

    TimeoutSync:可以認為是一個adaptor,其構造函數(shù)如下:
    public TimeoutSync(Sync sync, long timeout){…}
    具體上鎖的代碼靠構造函數(shù)傳入的sync實例來完成,其自身只負責監(jiān)測上鎖操作是否超時,可與SyncSet合用。

    Channel接口:代表一種具備同步控制能力的容器,你可以從中存放/讀取對象。不同于JDK中的Collection接口,可以把Channel看作是連接對象構造者(Producer)和對象使用者(Consumer)之間的一根管道。如圖所示:

    圖4 Concurrent包Channel接口示意圖

    通過和Sync接口配合,Channel提供了阻塞式的對象存取方法(put/take)以及可設置阻塞等待時間的offer/poll方法。實現(xiàn)Channel接口的類有LinkedQueue,BoundedLinkedQueue,BoundedBuffer,BoundedPriorityQueue,SynchronousChannel,Slot等。
     
    圖5 Concurrent包Channel接口部分類關系圖

    使用Channel我們可以很容易的編寫具備消息隊列功能的代碼,示例如下:
    代碼4
    1. Package org.javaresearch.j2seimproved.thread;
    2. Import EDU.oswego.cs.dl.util.concurrent.*;
    3. public class TestChannel {
    4.   final Channel msgQ = new LinkedQueue(); //log信息隊列
    5.   public static void main(String[] args) {
    6.     TestChannel tc = new TestChannel();
    7.     For(int i = 0;i < 10;i ++){
    8.       Try{
    9.         tc.serve();
    10.         Thread.sleep(1000);
    11.       }catch(InterruptedException ie){
    12.       }
    13.     }
    14.   }
    15.   public void serve() throws InterruptedException {
    16.     String status = doService();
    17. //把doService()返回狀態(tài)放入Channel,后臺logger線程自動讀取之
    18.     msgQ.put(status); 
    19.   }
    20.   private String doService() {
    21.     // Do service here
    22.     return "service completed OK! ";
    23.   }
    24.   public TestChannel() { // start background thread
    25.     Runnable logger = new Runnable() {
    26.       public void run() {
    27.         try {
    28.           for (; ; )
    29.             System.out.println("Logger: " + msgQ.take());
    30.         }
    31.         catch (InterruptedException ie) {}
    32.       }
    33.     };
    34.     new Thread(logger).start();
    35.   }
    36. }

    Excutor/ThreadFactory接口: 把相關的線程創(chuàng)建/回收/維護/調度等工作封裝起來,而讓調用者只專心于具體任務的編碼工作(即實現(xiàn)Runnable接口),不必顯式創(chuàng)建Thread類實例就能異步執(zhí)行任務。
    使用Executor還有一個好處,就是實現(xiàn)線程的“輕量級”使用。前面章節(jié)曾提到,即使我們實現(xiàn)了Runnable接口,要真正的創(chuàng)建線程,還是得通過new Thread()來完成,在這種情況下,Runnable對象(任務)和Thread對象(線程)是1對1的關系。如果任務多而簡單,完全可以給每條線程配備一個任務隊列,讓Runnable對象(任務)和Executor對象變成n:1的關系。使用了Executor,我們可以把上面兩種線程策略都封裝到具體的Executor實現(xiàn)中,方便代碼的實現(xiàn)和維護。
        具體的實現(xiàn)有: PooledExecutor,ThreadedExecutor,QueuedExecutor,F(xiàn)JTaskRunnerGroup等
        類關系圖如下:
     
    圖6 Concurrent包Executor/ThreadFactory接口部分類關系圖
    下面給出一段代碼,使用PooledExecutor實現(xiàn)一個簡單的多線程服務器
    代碼5
    1. package org.javaresearch.j2seimproved.thread;
    2. import java.net.*;
    3. import EDU.oswego.cs.dl.util.concurrent.*;
    4. public class TestExecutor {
    5.   public static void main(String[] args) {
    6.     PooledExecutor pool =
    7.         new PooledExecutor(new BoundedBuffer(10), 20);
    8.     pool.createThreads(4);
    9.     try {
    10.       ServerSocket socket = new ServerSocket(9999);
    11.       for (; ; ) {
    12.         final Socket connection = socket.accept();
    13.         pool.execute(new Runnable() {
    14.           public void run() {
    15.             new Handler().process(connection);
    16.           }
    17.         });
    18.       }
    19.     }
    20.     catch (Exception e) {} // die
    21.   }
    22.   static class Handler {
    23.     void process(Socket s){
    24.     }
    25.   }
    26. }

    限于篇幅,這里只是蜻蜓點水式的介紹了Concurrent包,事實上還有相當多有用的接口和類沒有提到,我們的配套光盤中附帶了Concurrent包和源代碼,感興趣的讀者可以自行分析。


    版權聲明
    本篇文章對您是否有幫助?  投票:         投票結果:     9       0
    作者其它文章: 作者全部文章
      評論人:javamonkey    參與分: 61928    專家分: 1660    來自: 北京海淀區(qū)三里河
    發(fā)表時間: 2003-9-3 上午9:19
    好文章,好書,出書后我一定要去買一本看看,哈哈
      評論人:javaone    參與分: 42017    專家分: 870 發(fā)表時間: 2003-9-3 上午11:08
    看來這本書值得一看!
      評論人:GLOBALone    參與分: 12    專家分: 0 發(fā)表時間: 2003-9-5 上午8:56
    看不太懂啊大哥!
    我知識java的初學者!
    應該看什么書啊?
    請各位大哥指點!
      評論人:GLOBALone    參與分: 12    專家分: 0 發(fā)表時間: 2003-9-5 上午8:58
    我只是java的初學者!看過thinking java 哪本書! 好多地方
    也是不太懂!我急啊!
      評論人:admin    參與分: 59502    專家分: 9080    來自: Java研究組織
    發(fā)表時間: 2003-9-15 上午11:21
    《J2SE進階》是寫給學習、使用Java 半年~1年以上的朋友讀的書。對于入門,看《Java Tutorial》(中文版是:Java語言導學)是最好的教材。
      評論人:mosa    參與分: 15    專家分: 0 發(fā)表時間: 2003-10-29 下午5:02
    文中所述第二個原因是JMM的固有問題,與DCL pattern無關。
    Symantec JIT太老了,而且有很多問題,Oracle DB的安裝程序似乎還在用,但必須在命令行中關掉jit。文中列出的JIT'ed code顯然經(jīng)過了極其aggressive(甚至是錯誤)的code scheduling,對照一下a[i] = new A()經(jīng)javac(或其它編譯器like jikes)產(chǎn)生的bytecode,GC分配返回的地址一般先存入stack,調用constructor,途徑local variable最后才aastore到數(shù)組a中,即使JIT可以優(yōu)化掉在stack和local variable中的存取,兩個calls,constructor和aastore(一般要進行type check)是無法避免的,而call是可能具有side effect的(比如說throw exception),不能跨過call超前寫入內(nèi)存里。即使高明的JIT可以inline這些calls,對內(nèi)存的存取有alias問題,一般JIT不會做如此aggressive的code motion。目前典型的JIT,如Hotspot,IBM JRE, JRocket,一般都能讓DCL正確運行在IA32和其它一些典型的architecture上。
    現(xiàn)在JMM的mailing list最熱鬧的其實是一些研究hardware memory model的人,個人認為Java程序員是完全不用害怕違反JMM而放棄使用一些典型的patterns。
    作者很多對synchronized使用的建議是很有價值的,另外兩個重要的參考來源是:
    Concurrent Programming in Java(Doug Lea)和Effective Java(Joshua Bloch)。
      評論人:shyguy    參與分: 20392    專家分: 560    來自: 西湖
    發(fā)表時間: 2003-10-30 下午2:48
    多謝mosa兄的精彩評述.

    我曾經(jīng)自己用softice跟蹤過sun jvm,希望研究其如何在windows平臺上分配內(nèi)寸和初始化實例,跟蹤無數(shù)步而無所獲得,終于放棄. 我也曾設想過研究Jikes的src,但限于個人能力精力有限,最終未敢有所動作.所以還是引用了別人的例子,就是那段過期的Symantec JIT編譯出來的代碼.

    我本是一個實用主義者,同時為一個java程序員,對于JVM的設計與實現(xiàn)并不深入.但寫書畢竟與實用不同,而我深知網(wǎng)友口水的威力,所以花費很多時間逡巡于國外的論壇和mailist,好好的學習了一些JVM深層次的東西,希望自己寫出來的內(nèi)容不至于貽笑大方.

    在DCL和JMM問題的討論上,我希望讀者能夠了解到這更多的是一個理論上的爭論,對于這些問題如何解決,則已經(jīng)超出了java程序員的學習范圍。而我自己在實用過程中也是放手大膽的在使用DCL.希望書中蹩腳的行文沒有誤導廣大讀者。

    同時,我想在本書的修訂版本中引入mosa兄的文字,作為補充參考,不知mosa兄意下如何。
      評論人:mosa    參與分: 15    專家分: 0 發(fā)表時間: 2003-10-31 下午2:12
    不必客氣,shyguy兄,我認為本文已經(jīng)是國內(nèi)有關Java/JVM研究最精辟的論述之一,雖然一直只看不說,也禁不住技癢狗尾續(xù)貂一次。關于JVM/CLI的問題以后可以多討論。
      評論人:Jagie    參與分: 73599    專家分: 3810    來自: 北京
    發(fā)表時間: 2003-11-20 下午4:48
    牛人啊,我還停留在使用api的階段。
      評論人:feng0000    參與分: 12    專家分: 0 發(fā)表時間: 2004-4-9 下午5:50
    請大蝦們指點:
    main()
    {
    thread = new Thread(this);
    thread.start();

    }
    run()
    {
      synchronized (this)
     { 
     f=true;
     if(f==true) sleep(1000);        語句1
     if(f==true) sleep(300);         語句2
        語句3;
     }
    }

    press()
    {
     f=false;
     語句4;
     thread = new Thread(this);
     thread.start();
    }
    thread1:run 進行同時 press 使得f=false,此時語句1還沒sleep完事 又新起線程thread2,使得f=true;thread1 sleep(300);  
    我的本意想thread 1 在press 時退出run()不執(zhí)行語句2。 
    請問用 wait()/notify()怎么實現(xiàn)?我想在 語句3處加notify(),在語句4處加wait(), 
    但是不得這兩個函數(shù)的使用要領,monitor不清楚,能給出源碼么?謝謝。
      評論人:pig345    參與分: 12    專家分: 0 發(fā)表時間: 2004-7-28 上午10:06
    不好意思,也來湊個熱鬧。

    Class ResSingleton {
    public static Resource res = new Resource();
    }

    這種,我以前的使用經(jīng)歷中好像失敗過。具體的環(huán)境忘了(好像是一個ejb項目)。
    轉自:http://www.javaresearch.org/article/showarticle.jsp?column=544&thread=8832

    posted on 2005-04-19 23:40 weidagang2046 閱讀(1366) 評論(0)  編輯  收藏 所屬分類: Java

    主站蜘蛛池模板: 99视频在线观看免费| 亚洲真人无码永久在线| 久久精品国产免费| 国产精品无码亚洲精品2021| 亚洲韩国在线一卡二卡| 亚洲免费日韩无码系列| 无码人妻精品一二三区免费| 无码国产精品一区二区免费式芒果| 美女一级毛片免费观看 | 国产成人1024精品免费| 亚洲av最新在线观看网址| 亚洲一级片在线观看| 久久精品九九亚洲精品| 亚洲精品亚洲人成在线观看| 亚洲福利在线播放| 免费亚洲视频在线观看| 日韩免费视频网站| 女人张腿给男人桶视频免费版 | 久久久久久久尹人综合网亚洲| 亚洲日本一区二区一本一道| 国产免费久久精品久久久| 在线成人a毛片免费播放 | 亚洲 欧洲 自拍 另类 校园| 亚洲综合男人的天堂色婷婷| 亚洲国产精品久久久久| 久久亚洲国产视频| 久久精品国产亚洲| 亚洲AV无码久久精品成人| 亚洲av午夜福利精品一区人妖| 亚洲精品午夜国产VA久久成人| 久久久久亚洲AV无码专区网站| 亚洲一级片内射网站在线观看| 亚洲AV无码专区日韩| 亚洲日本中文字幕一区二区三区| 免费一级国产生活片| 亚洲成年看片在线观看| MM131亚洲国产美女久久| 日韩亚洲变态另类中文| 亚洲国产精品va在线播放| 亚洲成a人片在线观看中文动漫 | 8x8×在线永久免费视频|