2016-02-12 124 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