2012-09-09 94 views
1

正如你所看到的,我是多線程新手,有點卡在這裏。對於我的程序,我需要一個線程(在下面的例子中爲PchangeThread),可以在程序執行期間的任何時刻從另一個線程開啓和關閉。 線程應在啓動時暫停,並在調用pixelDetectorOn()時繼續。多線程初學者問題

這兩個線程很可能不需要共享任何數據,除了啓動/停止標誌。無論如何,我還包括對主線程的引用,以防萬一。

但是,在下面的代碼中,唯一輸出的消息是「進入循環之前」,這表明線程從不會從wait()出於某種原因而被喚醒。我猜這是一種鎖定問題,但我一直無法弄清楚究竟發生了什麼問題。從主線程鎖定this.detector給了我相同的結果。此外,我想知道如果wait()/notify()範例真的是暫停和喚醒線程的方式。

public class PchangeThread extends Thread { 
    Automation _automation; 
    private volatile boolean threadInterrupted; 

    PchangeThread(Automation automation) 
    { 
    this._automation = automation; 
    this.threadInterrupted = true; 
    } 

    @Override 
    public void run() 
    { 
    while (true) { 
     synchronized (this) { 
     System.out.println("before entering loop"); 
     while (threadInterrupted == true) { 
      try { 
      wait(); 
      System.out.println("after wait"); 
      } catch (InterruptedException ex) { 
      System.out.println("thread2: caught interrupt!"); 
      } 
     } 
     } 
     process(); 
    } 
    } 

    private void process() 
    { 
    System.out.println("thread is running!"); 

    } 

    public boolean isThreadInterrupted() 
    { 
    return threadInterrupted; 
    } 

    public synchronized void resumeThread() 
    { 
    this.threadInterrupted = false; 
    notify(); 
    } 
} 

resumeThread()從主線程調用方式如下:

public synchronized void pixelDetectorOn(Context stateInformation) {   
     this.detector.resumeThread(); 
} 

detector是的PchangeThread實例的引用。 的「探測器」 -thread是該程序的主模塊中實例化方式如下:

detector=new PchangeThread(this); 
+0

此網站是爲了獲取工作代碼的評論。在SO上更好地詢問這種問題。現在遷移到那裏。 – sepp2k

回答

3

正如你所說,你需要保護訪問共享標誌。你聲明爲threadInterrupted易變,但仍然使用syncronized。你只需要一個。我更喜歡使用syncronized,因爲它使事情變得更簡單。多線程足夠複雜,保持簡單,除非你知道你需要更復雜的控制。這意味着任何時候讀取或寫入threadInterrupted,訪問應該是同步的。目前,您在setThreadInterrupt()isThreadInterrupted()中沒有這樣做。其次,你想盡可能小的代碼塊進行同步。在run()的內部,您正在通過內部循環進行同步。實際上,您只需要在讀取threadInterrupted時同步。當如上所述固定isThreadInterrupted()的實現時,可以直接使用它並從內部循環中刪除同步塊。

您在內部循環上同步的事實是導致您的代碼永遠不會打印「線程正在運行!」的錯誤。 PchangeThread獲取其自身的鎖並呼叫wait()掛起該線程。但是,此時線程仍然保持鎖定狀態。稍後,主線程調用resumeThread()以重啓線程。但是,該方法無法開始執行,因爲它必須首先等待獲取該鎖。但是,在通知PchangeThread之前,它永遠不會獲得鎖定。

您提供了兩種設置threadInterrupted的方法,但只有其中一個方法在值設置爲false時通知線程。你真的需要setThreadInterrupt()嗎?我希望你不要。如果保留它,當參數爲false時,它應該與resumeThread()相同。

最後,最好鎖定一個私有對象而不是實例本身。您可以完全控制私人鎖定對象。但是,任何引用您的線程實例的人都可以將其用作同步塊的鎖,這可能會導致很難發現死鎖。

您的代碼更改爲使用我的編輯:

public class PchangeThread extends Thread { 
    private final Object _lock = new Object(); 
    Automation _automation; 
    private final boolean _threadInterrupted; 

    PchangeThread(Automation automation) 
    { 
    _automation = automation; 
    _threadInterrupted = true; 
    } 

    @Override 
    public void run() 
    { 
    while (true) { 
     System.out.println("before entering loop"); 
     while (isThreadInterrupted()) { 
     try { 
      wait(); 
      System.out.println("after wait"); 
     } catch (InterruptedException ex) { 
      System.out.println("thread2: caught interrupt!"); 
     } 
     } 
     process(); 
    } 
    } 

    private void process() 
    { 
    System.out.println("thread is running!"); 

    } 

    public boolean isThreadInterrupted() 
    { 
    synchronized (_lock) { 
     return _threadInterrupted; 
    } 
    } 

    public void resumeThread() 
    { 
    synchronized (_lock) { 
     _threadInterrupted = false; 
     notify(); 
    } 
    } 
} 
+0

我認爲這種情況下使用同步是一點矯枉過正。使用CAS操作的AtomicBoolean將更加高效。 – LordDoskias

+0

@LordDoskias:在這種情況下,'AtomicBoolean'就足夠了。但是,OP是初學者,並不瞭解如何正確使用'synchronized'。我認爲在查看特殊情況之前最好充分理解基礎知識。如果明天代碼需要兩個變量不中斷更新,代碼將不得不被完全重寫。我們也不知道代碼需要多高效。獲取鎖定更加昂貴,但如果存在磁盤IO或用戶交互,則可能不會產生顯着差異。 – unholysampler

+0

感謝您的回答!這裏有兩點我不太明白:不等待()需要從同步上下文中調用,然後在運行後自行放棄鎖定。我收到「非法監視器狀態異常」。此外,不會通知()必須從鎖對象(_lock.notify())調用?我主要是做這個(希望有用的)編程實踐,所以我想我也會嘗試其他方法。最後我可能會堅持使用CAS操作,因爲這個線程必須執行一些非常耗費CPU資源的計算。 –

1

我個人會問自己,在這種情況下,以下問題:是對

isInterrupted

標誌只能通過設定主線程例如工作線程只是讀取它並根據標誌決定是否等待,但不更新它。或者它可以由主線程和工作線程來設置。

如果它是前者 - 去一個易變的布爾值。這樣工作線程就不會緩存volatile的值,並且會一直從內存中讀取它。這不會造成競爭條件,因爲只有1個線程會更新它 - 主要的線程。將其視爲發佈/訂閱場景。

如果您的方案屬於後一類 - 請使用AtomicBoolean變量。這兩種情況都將比synchronized關鍵字更有效,因爲你不會獲得任何鎖,但是在Atomic *變量的情況下,你將會使用比鎖獲取更輕量級的操作CAS

0

你的代碼沒有錯(雖然不是很理想)。 我跑了它,它打印所有預期的消息。有可能,你只是不要調用resumeThread()

一對夫婦的建議:

  • 不同步上線,使得它一個Runnable和同步。

  • 你想開始一些計算,但是要計算的數據是什麼?看起來他們以單獨的方式去。這是錯誤的根據。數據和控制都使用單通道。首選的方法是爲這樣的頻道使用隊列。例如,LinkedBlockingQueue已經以正確的方式同步。

+0

我調用resumeThread(),我已經通過該程序。該方法由於某種原因而未被執行。一旦達到resumeThread(),執行跳到調用者的末尾。在可運行的同步上有什麼優勢(除了我可以從另一個類繼承?)。工作線程必須實時(近)比較像素模式,因此傳遞任何內容可能不是一種選擇。我注意到了一些其他的東西:我使用的第三方jar生成了多個線程。我看不出爲什麼,但也許這是造成問題的原因。 –

+1

你用什麼IDE?嘗試另一個IDE,並從命令行通過JDK嘗試javac。 –

0

我懷疑任何人會讀這一點,但以防萬一有人就是想知道:

當我檢查調試日誌,我注意到一些奇怪的事情 - 它讀「調試停止了對不可編譯的源代碼:)無效;」。由於我無法想到可能導致此錯誤的源代碼中的任何內容,我猜想Netbeans在我使用的外部代碼的某些部分存在問題(它不是由斷點引起的,而且項目編譯得很好!) 。所以,我只是更新了我使用的最新版本的第三方庫。注意:在此之後,當我調用resumeThread()!時突然出現空指針異常。我檢查了我的代碼的其餘部分,並很快找到了該錯誤(的確,對該線程的引用爲空)。所以,總結一下:奇怪的行爲是由我的程序中的一個小錯誤引起的,但是外部jar中的某些東西導致了應該拋出的異常的抑制。出於好奇,我通過降級jar和「解開」錯誤再次進行檢查,並且再次吞下異常,並且調試器退出上述奇怪的消息。

Netbeans版本7.1。1