2012-03-01 200 views
15

我环顾四周,但还没有找到答案,所以我想确认一下。Java线程池/执行程序服务和wait()s - 线程和任务队列发生了什么?

说我有一个固定大小的线程池 - ExecutorService pool = Executors.newFixedThreadPool(5);

而且我有一些代码:

pool.execute(new Runnable(){ 
    try{ 
     Object waitForMe = doSomethingAndGetObjectToWaitFor(); 
     waitForMe.wait(); 
     doSomethingElse(); 
    }catch(Exception e){ throw new RunTimeException(e) } 

}); 

让我们假设上面的代码被称为几百倍。池中只有5个线程(所以上面的语句中只有5个应该在一个点上)。另外假设wait()位于一个对象上,该对象正在对第三方进行一些I/O调用,并在操作完成时等待回调,因此自然需要一段时间才能完成。

现在我的问题是,当其中一个任务达到wait()时的行为是什么,任务是否进入休眠状态,然后线程池中的线程将另一个任务从队列中取出并开始运行?

如果正在等待的任务进入睡眠状态,当它获得notify()并且醒来时会发生什么?线程是否回到线程池的队列中(在前面或后面),并等待5个线程中的一个线程能够继续执行它(即调用doSomethingelse())?或者执行它的线程也进入休眠状态,即5个执行程序线程中的一个线程等待任务(这是我所假设的)?或者执行程序线程接受另一个任务,并在第一个任务从wait()返回时被简单地中断?

回答

16

wait()是阻塞操作:

造成当前线程等待,直到其他线程调用notify()方法或notifyAll()

这意味着该线程池中将等待,但从外面看,目前的任务需要很长时间才能完成。这也意味着如果执行5个任务并且它们全部为wait(),则Executor不能处理剩余的任务,其中,等待队列中的等待

确实,执行程序线程本身进入休眠状态,允许其他线程切换并使用CPU(因此可以有数百个线程同时等待,并且系统仍然可以响应),但仍然线程“不可用”,并且受阻。

另一个有趣的功能是中断 - 如果线程等待或睡觉,你可以打断它。请注意,wait()Thread.sleep()均声明InterruptedException。通过ExecutorService,您只需拨打以下电话就可以充分利用此优势:future.cancel()future是将任务提交给ExecutorService时获得回报的对象)。

最后我认为你应该重新设计你的解决方案。取而代之的积极等待外部系统完成,提供了一个API与回调:

pool.execute(new Runnable(){ 
    try{ 
     doSomethingAndCallMeBackWhenItsDone(new Callback() { 
      public void done() { 
       doSomethingElse(); 
      } 
     }); 
    }catch(Exception e){ throw new RunTimeException(e) } 

}); 

这样的外部系统的API将简单地通知您结果已经准备就绪,你将不必等待,阻止ExecutorService 。最后,如果doSomethingElse()需要花费大量的时间,你甚至可能决定安排它和,而不是使用外部第三方I/O线:

pool.execute(new Runnable(){ 
    try{ 
     doSomethingAndCallMeBackWhenItIsDone(new Callback() { 
      public void done() { 
       pool.submit(new Callbale<Void>() { 
        public Void call() { 
         doSomethingElse(); 
        } 
       } 
      } 
     }); 
    }catch(Exception e){ throw new RunTimeException(e) } 

}); 

更新:你问该怎么办超时?这里是我的想法:

pool.execute(new Runnable(){ 
    try{ 
     doSomethingAndCallMeBackWhenItsDone(new Callback() { 
      public void done() { 
       doSomethingElse(); 
      } 
      public void timeout() { 
       //opps! 
      } 
     }); 
    }catch(Exception e){ throw new RunTimeException(e) } 

}); 

我想你可以在第三方侧实现超时,如果发生有超时,只需调用timeout()方法。

+1

感谢您的好评!通过回调选项,设置超时的最佳方式是什么(比如说,如果没有任何错误发生,则希望等待回调x秒)。我能想到的唯一方法是记下当前系统毫秒数并将其保存在一个列表中,并让另一个线程监视超出当前时间并触发错误的呼叫列表。我需要找到一本关于并发,回调等的好书。再次感谢! – NightWolf 2012-03-01 11:02:14

+1

@NightWolf:关于超时查看我更新的答案。当谈到一本好书时,[Java并发实践](http://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601)是必须的。 – 2012-03-01 11:06:05

+0

感谢您的更新和图书链接,好主意。可悲的是我对第三方没有任何控制权。 – NightWolf 2012-03-01 11:12:04

1

wait()无法知道任何有关胎面花纹。线程池无法知道wait()的任何内容。所以他们不能相互作用。

他们像往常一样工作 - wait()只是一个长时间运行的阻塞操作,线程池只是一个运行在有限的线程池上的runnable队列。

0

我会评论托马斯的回答,但我的声望不允许(但),对不起。

我知道这个问题已经过时了,但对于最终还是读完这个页面的人来说,看看Future,特别是番石榴的ListenableFuture,它可以让你注册回调和连锁未来,你的线程(并且因此将线程释放回池中以用于其他用途)。

0

所有5个线程将​​被阻止,应用程序将处于非生产状态。

添加到Tomasz答案,我想实现超时机制如下。

  Future<Long> futureResult = service.execute(myCallable); 
      Long result = null; 
      try{ 
       result = futureResult.get(5000, TimeUnit.MILLISECONDS); 
      }catch(TimeoutException e){ 
       System.out.println("Time out after 5 seconds"); 
       futureResult.cancel(true); 
      }catch(InterruptedException ie){ 
       System.out.println("Error: Interrupted"); 
      }catch(ExecutionException ee){ 
       System.out.println("Error: Execution interrupted"); 
      } 

除了TimeoutException,您可以在InterruptedException & ExecutionException取消未来。如果使用submit()而不是execute(),则框架本身会吞噬InterruptedException & ExecutionException