2016-02-12 127 views
3

下面是我編寫的代碼的簡化版本。基本上有一些對象接收消息,對它們做些什麼,然後傳遞它們(它們實現IState)和發送消息的對象(實現ISend)。問題是我得到非常深的堆棧跟蹤,最終導致堆棧溢出。我怎樣才能解決這個問題?如何限制堆棧深度

public class StackTraceMain { 
    private IState origin; 

    public static void main(String[] args) {   
     StackTraceMain s = new StackTraceMain(); 
     s.prepare(); 
     s.go(); 
    } 

    public void prepare(){ 
     Sender sendTo2 = new Sender(); 
     Sender sendTo1 = new Sender(); 
     origin = new State(sendTo2); 
     IState state2 = new State(sendTo1); 
     sendTo2.setTarget(state2); 
     sendTo1.setTarget(origin); 
    } 

    public void go(){ 
     origin.update(new DataTuple(0)); 
    } 

    private class State implements IState { 
     private final ISend sender; 

     public State(ISend sender) { 
      this.sender = sender; 
     } 

     @Override 
     public void update(DataTuple data) { 
      int num = data.getInteger(0); 
      num++; 
      System.out.println("Sending " + num + ", depth: " + Thread.currentThread().getStackTrace().length); 
      if (num < 1000) 
       sender.signal(new DataTuple(num)); 
     }  
    } 

    private class Sender implements ISend { 
     private IState target; 

     public void setTarget(IState target){ 
      this.target = target; 
     } 

     @Override 
     public void signal(DataTuple data) { 
      target.update(data);   
     }  
    } 
} 
+0

需要很長時間才能發現異常情況,因此需要我一段時間才能發佈。爲什麼要使用不同的事件總線幫助? – Johnny

回答

2

而不是使用無限遞歸函數調用,使用SingleThreadedExecutor,並使用它來調度更新調用。

由於此執行程序是單線程的,因此您不必擔心發生奇怪更改的併發操作。

爲了使這一變化,我們做一個全局線程池在應用程序的啓動:

public final static ExecutorService GLOBAL_APPLICATION_THREAD = Executors.newSingleThreadExecutor(); 

然後我們改變更新方法:

@Override 
public void update(DataTuple data) { 
    GLOBAL_APPLICATION_THREAD.execute(() -> { // Create lamba function 
     int num = data.getInteger(0); 
     num++; 
     System.out.println("Sending " + num + ", depth: " + Thread.currentThread().getStackTrace().length); 
     if (num < 1000) 
      sender.signal(new DataTuple(num)); 
     }  
    }); 
} 

當你再運行新創建的代碼中,您會發現堆棧大小保持不變,這是因爲Executor的成癮將遞歸展開爲看起來像循環的地方,您可以在循環中添加元素。

看看這是如何工作的最好方法是瞭解execute不會直接執行它,但它會將其放入待執行的任務等待行中,並且只有在整個功能堆棧結束時纔會執行執行一項新功能。在此Executor的循環可以被看作是以下幾點:

// Demonstration code only, may not feature best practices 
LinkedList<Runnable> q = new LinkedList<>(); 
while(true) { 
    Runnable task = q.remove(); // removes the first element 
    task.execute() 
} 

當您嘗試執行新的任務,它basicly確實q.add(...),因此直到當前運行的任務完成等待執行。

+0

這有效,但你能解釋爲什麼嗎? – Johnny

+1

@Johnny編輯了這篇文章,只是看到'Executor'作爲一個列表,您可以添加Runnables,並結合專用線程讀取其中的所有任務, – Ferrybig

0

您可以通過更改您的Java應用程序(或應用服務器)的參數-Xss防止StackOverflow

相反,如果你有太多深刻的通話,這是不可能找到的堆棧尺寸,以防止計算器你需要reenginering您的應用程序刪除一些呼叫一個合理的值。例如您可以將遞歸函數轉換爲標準循環

+0

我同意需要執行一些更改,但我不確定哪個更改。有兩個「IState」對象必須一遍又一遍地處理相同的消息。 – Johnny