2017-03-20 119 views
1

我有一個簡單的(或者至少我認爲它是)在線程中執行某些工作的Java應用程序。Java線程無法啓動(或需要很長時間才能啓動)

是這樣的:

public class Task implements Runnable { 

    public boolean running = false; 

    public void run() { 
     running = true; 
     // long running processing 
    } 
} 

... 

void startTask() { 
    Task task = new Task(); 
    Thread thread = new Thread(task); 
    thread.start(); 
    // I added this thinking the calling thread might terminate before 
    // the new thread starts 
    while (!task.running) { 
     try { 
      Thread.sleep(1); 
     } catch (InterruptedException ie) { 
      // log error 
    } 
} 

startTask()以上被稱爲響應於REST請求(這是一個Spring啓動應用程序)。它在我的開發機器(Windows 10,Oracle JDK)和Amazon EC2實例(Amazon Linux,OpenJDK)上運行良好,但不在Google Compute實例(Ubuntu 16.04,OpenJDK)上運行。在後一種情況下,工作線程永遠不會啓動(task.running永遠不會設置爲true),或者它有時在60+秒後啓動。我很困惑。

鑑於任務本身不是很複雜(再加上設置「運行」標誌是它的第一件事情,而這從來沒有發生)的事實導致我認爲這是一個奇怪的JVM /系統相關問題,但我真的不知道。

最令人沮喪的是它的工作有時(通常是我第一次上傳/運行它重建後)。並且從未在我的電腦上工作過。

編輯:我曾嘗試在Ubuntu中使用Oracle JRE,但同樣缺乏成功。

2nd編輯:是的,我在這裏寫了示例代碼時犯了一些錯誤。固定。

回答

2

這不是您應該在Java中啓動線程任務的一種方式。在這種情況下,您只需調用run方法即可。要運行線程,您應該使用啓動方法:

thread.start(); 
+0

你是對的。我在示例代碼中犯了一個錯誤 - 我編輯了這個問題,現在它匹配我的實際代碼(它應該調用'thread.start()') – bobsyouruncle

3

Runnable是一個接口,因此您正在創建一個名爲Task的新接口!但是,您還提供了run()方法的實現。這不會編譯。

也許,這就是你想要做什麼:

class Task implements Runnable { 
    public boolean running = false; 

    @Override 
    public void run() { 
     running = true; 
     // long running processing 
    } 
} 

另一個錯誤是,你直接調用線程的run()方法! 你應該調用start()來代替。

一個示例代碼可能類似於以下內容:

void startTask() { 
    Task task = new Task(); 
    Thread thread = new Thread(task); 
    thread.start(); 
    // I added this thinking the calling thread might terminate before 
    // the new thread starts 
    while (!task.running) { 
     try { 
      Thread.sleep(1); 
     } 
     catch (InterruptedException ie) { 
      // log error 
     } 
    } 
} 
+1

是的,謝謝,我填滿了示例代碼。實際的代碼是你建議的。我已經更新了示例。謝謝。 – bobsyouruncle

+0

沒問題,我希望這有幫助。 – omt66

1

你的代碼是不是線程安全的,因爲您從多個線程同時訪問一個單一的變量,因爲你從來沒有使用過任何安全訪問結構來訪問這個變量,這意味着內部java代碼優化器可能會將while循環優化爲while(true)循環。

雖然您可以聲明變量volatile來解決問題,但真正的解決方案將使用Object.waitObject.notifyAll來處理等待期。

監聽器已經開始主線程後,它應該進入一個while循環,在之間,等待檢查條件的synchronized塊內,例如:

thread.start(); 
// I added this thinking the calling thread might terminate before 
// the new thread starts 
try { 
    synchronized (task) { 
     while (!task.running) { 
      task.wait(); 
     } 
    } 
} catch (InterruptedException ie) { 
    // log error 
} 

然後你的任務裏,你需要設置運行到true,然後通知所有線程。

synchronized (this) { 
    this.running = true; 
    this.notifyAll(); 
} 
1

您還需要您的running變量標記爲volatile,如下圖所示,否則,其他線程將無法保證看到的變化到running變量。

private volatile boolean running; 

更改volatile變量總是可見的其他線程

你可以看看here更多volatile

1

我認爲這是一個內存模型/同步問題。你有一個線程寫running標誌,另一個線程讀而不做任何同步。這可能導致閱讀(在這種情況下,主線程)線程看不到由寫入(在這種情況下爲子)線程所做的更新running

解決方案:

  1. 使用synchronized方法來讀取和更新running標誌。
  2. 聲明runningvolatile

最令人沮喪的事情是它的工作原理有時(通常是我第一次上傳/後重建運行)。並且從未在我的電腦上工作過。

涉及不正確同步的錯誤是非常難以追查的。問題的根源在於,在現代多核處理器上,代碼的工作原理是......或不是......取決於內存緩存是否被刷新。這可能取決於您擁有多少物理內核,系統的繁忙程度,(OS提供的)線程日程表的行爲。而當您使用調試器或添加跟蹤記錄時,即負責來更改您嘗試追蹤的行爲。

最好的策略如下:

  1. 據介紹java.util.concurrent提供可能使用更高級別的併發性功能*類。

  2. 避免使用裸線程,共享變量,互斥鎖等。 (他們是很難正確使用。)

  3. 如果/當你需要使用低級別的原語:

    • 確保你真正瞭解了Java內存模型,並
    • 分析你的代碼要非常小心,以確保它沒有經典的競爭條件和內存訪問危險。
相關問題