在后台线程中运行的任务的可编辑队列

时间:2015-10-02 19:24:35

标签: java multithreading queue executorservice

我知道这个问题已被多次回答,但我很难理解它是如何运作的。

因此,在我的应用程序中,用户必须能够选择将添加到队列中的项目(使用ListViewObservableList<Task>中显示),并且每个项目都需要由{顺序处理{1}}。

该队列也应该是可编辑的(更改顺序并从列表中删除项目)。

ExecutorService

private void handleItemClicked(MouseEvent event) { if (event.getClickCount() == 2) { File item = listView.getSelectionModel().getSelectedItem(); Task<Void> task = createTask(item); facade.getTaskQueueList().add(task); // this list is bound to a ListView, where it can be edited Future result = executor.submit(task); // where executor is an ExecutorService of which type? try { result.get(); } catch (Exception e) { // ... } } } 尝试但是我无法控制队列 我读到了executor = Executors.newFixedThreadPool(1)和队列,但我很难理解它,因为我对并发很新。

我需要在后台线程中运行该方法ThreadPoolExecutor,以便UI不会冻结,我该怎样才能做到最好?

总结:如何实现可由后台线程编辑和顺序处理的任务队列?

请帮我搞清楚

修改 使用vanOekel的handleItemClicked类帮助了我,现在我想将任务列表绑定到SerialTaskQueue

ListView

显然这不起作用,因为它期待一个ObservableList。有一种优雅的方式吗?

1 个答案:

答案 0 :(得分:1)

我能想到的最简单的解决方案是在执行程序之外维护任务列表,并使用回调为执行程序提供下一个任务(如果可用)。不幸的是,它涉及任务列表上的同步和AtomicBoolean以指示正在执行的任务。

回调只是一个Runnable,它包装原始任务以运行,然后“回调”以查看是否还有另一个任务要执行,如果是,则使用(后台)执行程序执行它。 / p>

需要同步以使任务列表按顺序保持在已知状态。任务列表可以由两个线程同时修改:通过执行程序(后台)线程中运行的回调和通过UI前台线程执行的handleItemClicked方法。这反过来意味着,例如,当任务列表为空时,它永远不会完全知道。为了使任务列表按顺序保持在已知的固定状态,需要同步任务列表。

这仍然会留下一个模糊的时刻来决定任务何时可以执行。这就是AtomicBoolean的用武之地:值集总是立即可用并由任何其他线程读取,compareAndSet方法将始终确保只有一个线程获得“OK”。

组合同步和AtomicBoolean的使用允许创建一个具有“临界区”的方法,可以同时由前台线程和后台线程调用以触发执行新任务如果可能的话。以下代码的设计和设置方式可以存在一个这样的方法(runNextTask)。优良作法是使并发代码中的“关键部分”尽可能简单明了(反过来,通常会导致有效的“关键部分”)。

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

public class SerialTaskQueue {

    public static void main(String[] args) {

        ExecutorService executor = Executors.newSingleThreadExecutor();
        // all operations on this list must be synchronized on the list itself.
        SerialTaskQueue tq = new SerialTaskQueue(executor);
        try {
            // test running the tasks one by one
            tq.add(new SleepSome(10L));
            Thread.sleep(5L);
            tq.add(new SleepSome(20L));
            tq.add(new SleepSome(30L));

            Thread.sleep(100L);
            System.out.println("Queue size: " + tq.size()); // should be empty
            tq.add(new SleepSome(10L));

            Thread.sleep(100L);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdownNow();
        }
    }

    // all lookups and modifications to the list must be synchronized on the list.
    private final List<Runnable> tasks = new LinkedList<Runnable>();
    // atomic boolean used to ensure only 1 task is executed at any given time
    private final AtomicBoolean executeNextTask = new AtomicBoolean(true);
    private final Executor executor;

    public SerialTaskQueue(Executor executor) {
        this.executor = executor;
    }

    public void add(Runnable task) {

        synchronized(tasks) { tasks.add(task); }
        runNextTask();
    }

    private void runNextTask() {
        // critical section that ensures one task is executed.
        synchronized(tasks) {
            if (!tasks.isEmpty()
                    && executeNextTask.compareAndSet(true, false)) {
                executor.execute(wrapTask(tasks.remove(0)));
            }
        }
    }

    private CallbackTask wrapTask(Runnable task) {

        return new CallbackTask(task, new Runnable() {
            @Override public void run() {
                if (!executeNextTask.compareAndSet(false, true)) {
                    System.out.println("ERROR: programming error, the callback should always run in execute state.");
                }
                runNextTask();
            }
        });
    }

    public int size() {
        synchronized(tasks) { return tasks.size(); }
    }

    public Runnable get(int index) {
        synchronized(tasks) { return tasks.get(index); }
    }

    public Runnable remove(int index) {
        synchronized(tasks) { return tasks.remove(index); }
    }

    // general callback-task, see https://stackoverflow.com/a/826283/3080094
    static class CallbackTask implements Runnable {

        private final Runnable task, callback;

        public CallbackTask(Runnable task, Runnable callback) {
            this.task = task;
            this.callback = callback;
        }

        @Override public void run() {
            try {
                task.run();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    callback.run();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // task that just sleeps for a while
    static class SleepSome implements Runnable {

        static long startTime = System.currentTimeMillis();

        private final long sleepTimeMs;
        public SleepSome(long sleepTimeMs) {
            this.sleepTimeMs = sleepTimeMs;
        }
        @Override public void run() {
            try { 
                System.out.println(tdelta() + "Sleeping for " + sleepTimeMs + " ms.");
                Thread.sleep(sleepTimeMs);
                System.out.println(tdelta() + "Slept for " + sleepTimeMs + " ms.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private String tdelta() { return String.format("% 4d ", (System.currentTimeMillis() - startTime)); }
    }
}

更新:如果需要连续执行任务组,请查看已调整的实施here