Skip to content

Latest commit

 

History

History
78 lines (62 loc) · 6.4 KB

File metadata and controls

78 lines (62 loc) · 6.4 KB

15.1 使用List的方法

《《《 返回首页
《《《 上一节

使用List的方法

让我们看看在待办事项管理器中使用其中一些方法的例子。 在上一章中,我们考虑使用关闭功能在一个基于队列的类中组织一天的任务。 扩大应用范围的一个有用的方法是拥有许多这种类型的对象,每个对象代表未来一天计划的任务。 我们将在一个 List 中存储对这些对象的引用,以便将它表示的将来的天数编入索引(保持简单并避免处理 java.util.Calendar 的令人厌恶的细节)。 因此,今天计划的任务队列将存储在列表的元素 0 处,明天排队等待在元素1处等待。 例 15-1 显示了调度程序。

15-1。 基于列表的任务调度程序

   public class TaskScheduler {
     private List<StoppableTaskQueue> schedule;
     private final int FORWARD_PLANNING_DAYS = 365;
     public TaskScheduler() {
       List<StoppableTaskQueue> temp = new ArrayList<StoppableTaskQueue>();
       for (int i = 0 ; i < FORWARD_PLANNING_DAYS ; i++) {
         temp.add(new StoppableTaskQueue());
       }
       schedule = new CopyOnWriteArrayList<StoppableTaskQueue>(temp); //1
     }
     public PriorityTask getTask() {
       for (StoppableTaskQueue daysTaskQueue : schedule) {
         PriorityTask topTask = daysTaskQueue.getTask();
         if (topTask != null) return topTask;
       }
       return null; // no outstanding tasks - at all!?
     }
     // 在午夜时分,移除并关闭第0天的队列,将其任务分配到新的第0天,并在计划范围内创建新的一天队列
     public void rollOver() throws InterruptedException{
       StoppableTaskQueue oldDay = schedule.remove(0);
       Collection<PriorityTask> remainingTasks = oldDay.shutDown();
       StoppableTaskQueue firstDay = schedule.get(0);
       for (PriorityTask t : remainingTasks) {
         firstDay.addTask(t);
       }
       StoppableTaskQueue lastDay = new StoppableTaskQueue();
       schedule.add(lastDay);
     }
     public void addTask(PriorityTask task, int day) {
       if (day < 0 || day >= FORWARD_PLANNING_DAYS)
         throw new IllegalArgumentException("day out of range");
       StoppableTaskQueue daysTaskQueue = schedule.get(day);
       if (daysTaskQueue.addTask(task)) return; //2
       // StoppableTaskQueue.addTask仅在已关闭的队列上调用时返回false。 在这种情况下,它现在也会被移除,所以再次尝试是安全的。
       if (! schedule.get(0).addTask(task)) {
         throw new IllegalStateException("failed to add task " + task);
       }
     }
   }

尽管这个例子主要是为了展示使用 List 接口方法而不是去探索任何特定的实现,但是我们不能在没有选择的情况下进行设置。因为选择中的主要因素是应用程序的并发需求,所以我们需要现在考虑他们。它们非常简单:消费或制作任务的客户只会读取表示日程表的 List,因此(一旦构建完成),它唯一的写作场合就是在一天结束时。此时,当天的队列将从计划中删除,并在最后添加一个新的队列(“计划范围”,我们在示例中将其设置为一年)。在这种情况发生之前,我们不需要排除客户端使用当天的队列,因为示例 14.1StoppableTaskQueue 设计确保了一旦队列停止后它们能够以有序的方式完成。因此,唯一需要排除的是确保客户端在滚动过程正在改变其值时不会尝试读取日程表。

如果您回想一下第 11.5.3 节中对 CopyOnWriteArrayList 的讨论,您可能会发现它很好地满足了这些要求。它优化了阅读权限,符合我们的一项要求。在写操作的情况下,它同步的时间足够长,以创建其内部支持阵列的新副本,从而满足了防止读和写操作之间干扰的其他要求。

选择实现后,我们可以理解示例 15.1 的构造函数;写入列表的代价很高,因此使用转换构造函数在一次操作中设置一年的任务队列(行// 1)是明智的。

getTask 方法很简单;我们只需从今天的队列开始迭代任务队列,查找计划任务。如果该方法找不到任何未完成的任务,则返回 null - 并且如果发现无任务的日期值得注意,我们应该如何庆祝一个无任务的年份?

每天午夜,系统会调用 rollOver 方法,实现关闭旧日任务队列的悲伤仪式,并将其中剩余的任务转移到新的一天。这里的一系列事件很重要。rollOver 首先从列表中删除队列,此时生产者和消费者可能仍然要插入或删除元素。然后它调用 StoppableTaskQueue.shutDown,正如我们在例 14-1 中看到的那样,返回队列中剩余的任务并保证不会再添加更多任务。根据它们进展的程度,addTask 的调用将完成或将返回 false,表示由于队列关闭而失败。

这激发了 addTask 的逻辑:StoppableTaskQueueaddTask 方法可以返回 false 的唯一情况是被调用的队列已经停止。由于停止的唯一队列是第 0 天的队列,因此 addTask 的返回值 false 必须来自生产者线程在午夜翻转前获取对该队列的引用。在这种情况下,列表的元素 0 的当前值现在是新的第 0 天,并且再次尝试是安全的。如果第二次尝试失败,该线程已被暂停 24 小时!

请注意,rollOver 方法非常昂贵;它将两次写入日程表,并且由于日程表由 CopyOnWriteArrayList 表示(请参见第 15.2.3 节),因此每次写入都会导致整个备份数组被复制。赞成这种实现选择的论点是,与在 getTask 上进行的调用次数相比,rollOver 非常少被调用,该调用迭代了计划。CopyOnWriteArrayList 的替代方案将是一个 BlockingQueue 实现,但是在很少使用的 rollOver 方法中所提供的改进将以减慢经常使用的 getTask 方法为代价,因为队列迭代器不打算用于性能 - 危急情况。

   listIterator<StoppableTaskQueue> getSubSchedule(int startDay, int endDay) {
     return schedule.subList(startDay, endDay).listIterator();
   }

这种观点对今天来说可能会很好,但是我们必须记得在午夜时分抛弃它,因为删除和添加条目的结构性变化将使其无效。

《《《 下一节
《《《 返回首页