更新: :这是 我对哈希定时轮的实现. 。如果您有提高性能和并发性的想法,请告诉我。(2009 年 1 月 20 日)

// Sample usage:
public static void main(String[] args) throws Exception {
    Timer timer = new HashedWheelTimer();
    for (int i = 0; i < 100000; i ++) {
        timer.newTimeout(new TimerTask() {
            public void run(Timeout timeout) throws Exception {
                // Extend another second.
                timeout.extend();
            }
        }, 1000, TimeUnit.MILLISECONDS);
    }
}

更新: :我通过使用解决了这个问题 分层和散列定时轮. 。(2009 年 1 月 19 日)

我正在尝试在 Java 中实现一个特殊用途的计时器,它针对超时处理进行了优化。例如,用户可以注册一个具有截止时间的任务,并且计时器可以在截止时间结束时通知用户的回调方法。在大多数情况下,注册的任务将在很短的时间内完成,因此大多数任务将被取消(例如task.cancel()) 或重新安排到未来(例如task.rescheduleToLater(1, TimeUnit.SECOND))。

我想使用这个计时器来检测空闲套接字连接(例如当10秒内没有收到消息时关闭连接)和写入超时(例如当写入操作在 30 秒内未完成时引发异常。)在大多数情况下,不会发生超时,客户端将发送消息并发送响应,除非出现奇怪的网络问题。

我无法使用 java.util.Timer 或 java.util.concurrent.ScheduledThreadPoolExecutor 因为它们假设大多数任务都应该超时。如果任务被取消,取消的任务将存储在其内部堆中,直到调用 ScheduledThreadPoolExecutor.purge() 为止,这是一个非常昂贵的操作。(也许是 O(NlogN) ?)

在我在 CS 课程中学到的传统堆或优先级队列中,更新元素的优先级是一项昂贵的操作(在许多情况下为 O(logN)),因为它只能通过删除元素并使用新的优先级值。像斐波那契堆这样的堆有O(1)的decreaseKey()和min()操作时间,但我至少需要的是快速的increaseKey()和min()(或decreaseKey()和max())。

您知道针对该特定用例高度优化的任何数据结构吗?我正在考虑的一种策略是将所有任务存储在哈希表中,并每秒左右迭代所有任务,但这并不是那么美妙。

有帮助吗?

解决方案

如何试图通常情况下的分离移交,事情从错误的情况下快速完成?

使用两个哈希表和一个优先级队列。当任务开始时它被放在哈希表,如果它快速完成它得到的O(1)时间删除。

在扫描每一秒钟哈希表并且已经有很长一段时间的任何任务,说0.75秒,无法移动到优先级队列。优先级队列应该始终体积小,易于处理。这假定一秒比你正在寻找的超时次数少得多。

如果扫描哈希表太慢,则可以使用两个哈希表,基本上由一种用于偶数秒,一个用于奇数秒。当任务开始它被放在当前的哈希表。每秒钟移动从非当前哈希表中的所有任务分为优先级队列和交换的哈希表,以便在当前哈希表现在是空的和非当前表包含任务之一两秒前之间展开。

有选项的不仅仅是使用优先队列复杂,但很容易实现的应该是稳定的。

其他提示

据我所知(我写了一篇关于新优先级队列的论文,其中还回顾了过去的结果),没有优先级队列实现能够获得斐波那契堆的界限以及恒定时间增加键。

从字面上理解有一个小问题。如果您可以在 O(1) 中获得增加键,那么您可以在 O(1) 中获得删除——只需将键增加到+无穷大(您可以使用一些标准摊销技巧来处理充满大量+无穷大的队列)。但如果 find-min 也是 O(1),则意味着 delete-min = find-min + delete 变为 O(1)。这在基于比较的优先级队列中是不可能的,因为排序界限意味着(插入所有内容,然后逐一删除)

n * 插入 + n * 删除最小值 > n log n。

重点在这里 就是如果你想要一个优先级队列支持O(1)的increase-key,那么你必须接受以下惩罚之一:

  • 不是基于比较。实际上,这是解决问题的一个很好的方法,例如 vEB树.
  • 插入时接受 O(log n),创建堆时也接受 O(n log n)(给定 n 个起始值)。这太糟糕了。
  • 接受 O(log n) 进行查找最小值。如果您从未真正这样做过,这是完全可以接受的 find-min(不附带删除)。

但是,据我所知,没有人做过最后一个选择。我一直将其视为在数据结构的基本领域取得新成果的机会。

使用散列调速轮 - 谷歌“散列层次同步轮以获取更多信息。这是由人们在这里所作的答案概括。我宁愿具有大车轮尺寸的散列定时轮分层定时车轮。

哈希和O(logN)的结构的一些组合应该做什么你问。

我很想跟你分析问题的方式狡辩。在你上面的评论,你说

  

由于更新将发生非常非常频繁。比方说,我们每个连接发送M个消息则整体时间成为O(MNlogN),这是相当大的。 - 植信李(6小时前)

这是绝对正确的,只要它不用。但我知道大部分人都专注于成本的每封邮件的,从理论上说,你的应用程序有更多的工作要做,很明显这将需要更多的资源。

所以,如果您的应用程序有一个十亿插座开放的同时的(是真的有可能?)插入成本每封邮件只有约60比较。

我敢打赌钱,这是过早的优化:你有没有实际上像CodeAnalyst或VTune™可视化性能分析工具测量的瓶颈,在你的系统

总之,有的做你问什么,一旦你刚才决定,没有任何一个结构将做你想要的方式可能是无限多的,你想的不同算法的优势和劣势的某种组合。

一一种可能是在插座域中的N个划分为一些数量的尺寸B的桶,然后散列每个套接字到其中的一个(N / B)的桶。在该桶是一个堆(或其他)与为O(log B)更新时间。如果一个上界n不是预先固定的,而是可以变化,则可以动态地创建多个桶,其中增加了一个小的并发症,但肯定是可行的。

在最坏的情况下,看门狗定时器必须搜索到期(N / B)队列,但我假定不需要看门狗定时器杀空闲套在任何特定的顺序<!/ em>的  也就是说,如果10座去了最后一个分片空闲时,它不必搜索一个域超时第一,处理它,然后找到超时秒一个,等它只是已扫描的(N / B)组水桶和枚举所有超时。

如果您不满意提着水桶的线性阵列,可以使用队列的优先级队列,但要避免更新每个消息队列,否则你回来,你开始。取而代之的是,一段时间以来,这小于实际超时。 (喂,3/4或7/8那个),你只把低级别的队列进入高层次的队列,如果是时间最长的超过了。

和在说明明显的风险,你不希望在键入的过去的时候你的队列。键应启动的时间。对于在队列中的每个记录,经过的时间就必须不断更新,但每个记录的开始时间不会改变。

有一种非常简单的方法可以在 O(1) 中完成所有插入和删除,利用以下事实:1) 优先级基于时间,2) 您可能有少量固定的超时持续时间。

  1. 创建一个常规 FIFO 队列来保存所有在 10 秒内超时的任务。由于所有任务都有相同的超时持续时间,因此您只需插入到末尾并从开头删除即可保持队列排序。
  2. 为具有 30 秒超时持续时间的任务创建另一个 FIFO 队列。为其他超时持续时间创建更多队列。
  3. 要取消,请从队列中删除该项目。如果队列被实现为链表,则这是 O(1)。
  4. 重新调度可以通过取消插入来完成,因为这两个操作都是 O(1)。请注意,任务可以重新安排到不同的队列。
  5. 最后,为了将所有 FIFO 队列组合成一个整体优先级队列,让每个 FIFO 队列的头部参与到常规堆中。该堆的头部将是所有任务中超时时间最早的任务。

如果有 m 个不同的超时持续时间,则整个结构的每个操作的复杂度为 O(log m)。由于需要查找要插入到哪个队列,插入的时间复杂度为 O(log m)。恢复堆的Remove-min是O(log m)。取消的时间复杂度为 O(1),但如果要取消队列的头部,最坏情况下的时间复杂度为 O(log m)。因为 m 是一个很小的固定数,所以 O(log m) 本质上是 O(1)。它不随任务数量而扩展。

您特定的方案提出了一种环形缓冲器我。如果最大。超时时间为30秒,并且我们想获得套接字至少每隔十分之一秒,那么使用的300双链表,一个用于在该周期的第二的各第十一个缓冲器。要对项目“increaseTime”,从它在列表中删除它,并把它添加到一个用于其新的第十秒周期(包括固定时间的操作)。当一个周期结束时,(可能通过将其供给到回收线程)收获任何在当前列表遗留和推动当前列表指针。

您已经有了队列中的项目数硬限制 - 还有就是TCP套接字限制

因此,问题是有界的。我怀疑任何聪明的数据结构会比使用内置类型慢。

是否有一个很好的理由不使用java.lang.PriorityQueue?不会删除()处理您的日志(N)的时间取消操作?然后根据时间,直到在队列前面的项目实现自己的等待。

我认为存储在列表中的所有任务,并通过他们的迭代是最好的。

您必须(将)一些非常结实的机器上运行的服务器获取到了极限,其中这个成本将是重要的?

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top