如何实现延迟非阻塞的函数调用

11

我想在延迟一段时间后调用 HashSet 的 add 函数,但不阻塞当前线程。有没有简单的解决方案可以实现类似这样的功能:

Utils.sleep(1000, myHashSet.add(foo)); //added after 1 second
//code here runs immediately without delay
...

1
以下是您问题的直接答案。但是,您试图做的似乎相当不自然,这表明您可能应该寻找完全不同的解决方案。您是否想提供更多上下文,说明为什么要延迟添加? - Jochen
我正在使用 Storm 实现一个网络爬虫。要爬取的 URL 是由包含线程 ID 和论坛 ID 的模式生成的。该爬虫的性质只允许在任何时间处理一个板块的一个 URL。我的 HashSet 包含所有当前可空闲爬取的板块 ID。单个 URL 的爬取可能因不同原因而失败(如:线程被删除,404 等)。有些原因可以重试爬取。这些原因的信息保存在一个没有锁定的 DB 中,因此在决定是否重试之前应该有一些延迟。 - Thomas
那听起来有些不必要的复杂。为什么抓取线程不能处理返回值,并在发生可恢复故障时直接重试,或者至少将URL添加回地图(队列可能更好)? - Jochen
Storm的本质使得这个问题变得有些复杂,但也带来了易于扩展和容错性的好处。 - Thomas
3个回答

14
你可以使用ScheduledThreadPoolExecutor.schedule: ScheduledThreadPoolExecutor.schedule
ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);

exec.schedule(new Runnable() {
          public void run() {
              myHashSet.add(foo);
          }
     }, 1, TimeUnit.SECONDS);

它将在单独的线程上延迟1秒后执行您的代码。但是请注意,同时从不同的线程修改集合myHashSet可能会导致并发修改问题,如果您需要在不同的线程中同时修改集合或尝试遍历它,则需要使用锁。


仅仅在添加操作期间不迭代集合是不够的,因为无法保证更改的内存可见性。 - Oz Molaim

12

最基本的解决方案是:

    new Thread( new Runnable() {
        public void run()  {
            try  { Thread.sleep( 1000 ); }
            catch (InterruptedException ie)  {}
            myHashSet.add( foo );
        }
    } ).start();

与ThreadPoolExecutor相比,这里的后台操作要少得多。ThreadPoolExecutor可以方便地控制线程数,但如果您正在创建大量休眠或等待的线程,则限制它们的数量可能会对性能产生更大的负面影响。

如果您尚未处理过此问题,则需要在myHashSet上进行同步。请记住,为了使其起到作用,您必须在任何地方进行同步。还有其他的处理方法,例如使用Collections.synchronizedMap或ConcurrentHashMap。


3
Thread.sleep() 是一个阻塞调用。创建一个新线程不会阻塞主线程,但在并行处理中仍然会阻塞一个线程。 - Pran Kumar Sarkar


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接