我正在尝试用Java编写一个问题,我必须执行一堆任务。
问题
执行由多个任务组成的作业,这些任务之间具有依赖关系。
作业将有一个任务列表,每个此类任务将进一步列出后续任务列表(每个后续任务都有自己的后续任务 - 您可以在此处看到递归性质)。如果 -
,每个后继任务都可以开始执行它被配置为在部分执行其前任任务时执行。在这种情况下,前任任务将通知它已部分完成并且我的后续任务可以开始
成功完成其前任任务。
示例
有2个初始任务A和B的工作.A有2个后继任务M和N.B有1个后继任务P. P有2个后继任务Y和Z.
M可以从其前任任务A的部分完成开始。 Z可以开始部分完成其前任任务P. N,P和Y只能在完成其前任任务A,B和P时才能启动。
我必须设计这样的工作流程/工作的执行。在设计中,我们必须确认先前任务发送的部分完成事件,以便可以启动其后继任务。我该怎么办呢?在并发中是否有适合此问题的设计模式?
答案 0 :(得分:7)
看看akka - http://akka.io
使用akka创建actor(事件驱动,并发实体以异步方式处理消息)
每个任务都可以表示为一个演员(你选择何时开火)
你可以在部分完成或完全完成时触发其他演员(任务)(实际上你可以随时触发它们)
答案 1 :(得分:5)
您的问题看起来像是Java ForkJoin Framework的一个很好的用例。您可以将任务实现为RecursiveAction
或RecursiveTask
(取决于您是否需要返回值),这将在您需要的任何条件下启动其子任务。您还可以控制子任务是按顺序还是并行运行。
示例:
public class TaskA extends RecursiveAction {
// ...
protected void compute() {
if (conditionForTaskM) {
TaskM m = new TaskM();
// Run task M asynchronously or use m.invoke() to run it synchronously.
invokeAll(m);
}
// Run task N at the end of A
invokeAll(new TaskN());
}
}
您需要ForkJoinPool的实例来运行您的任务:
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
pool.submit(new TaskA());
// Properly shutdown your pool...
}
此示例在实现示例问题的一部分时非常简单。但一般来说,ForkJoin框架允许您创建树状结构的任务,其中每个父任务(例如A,B和P)允许您控制其直接子任务的执行。
答案 2 :(得分:5)
让我们从几个假设开始简化。
:)
基于以上假设,我创建了https://gist.github.com/ankgupta/98148b8eead2fbbc2bbb
interface Task{
public void doTask();
}
----EE.java
import java.util.*;
public class EE {
private static EE ee = new EE();
Map<String, Task> tasks = new HashMap<>();
private EE(){ }
public static EE getEE(){
return ee;
}
public void register(String event, Task t){
tasks.put(event, t);
}
public void message(String event){
Task t = tasks.get(event);
if(t!=null)
t.doTask();
}
}
class TaskA implements Task{
public void doTask(){
System.out.println("TaskA working!");
EE.getEE().message("TASKA_PARTIAL");
System.out.println("TaskA still working!");
EE.getEE().message("TASKA_COMPLETE");
}
}
class TaskB implements Task{
public void doTask(){
System.out.println("TaskB working!");
}
}
class TaskC implements Task{
public void doTask(){
System.out.println("TaskC working!");
}
}
public class Main{
public static void main(String[] args){
EE ee = EE.getEE();
ee.register("STARTA", new TaskA());
ee.register("TASKA_PARTIAL", new TaskB());
ee.register("TASKA_COMPLETE", new TaskC());
ee.message("STARTA");
}
}
改善上述
答案 3 :(得分:4)
在模式使用方面有两个选项,但实际上它们非常相似。在任何一种情况下,循环依赖性情况都需要作为任务依赖性配置中的错误来处理。例如A - &gt; B - &gt;一个
在每个任务[i]完成后,它将通知调解员,调解员将通知任务[i]的所有后继任务。当执行引擎作为Mediator要使用的数据结构启动时,将读取任务依赖关系图。
任务依赖关系图将在引擎启动时被读取,并且每个任务将在主题[i]
上订阅其前任任务的MessageBus(task [i])完成消息当每个任务[i]完成时,它将向主题[i]中的MessageBus发送完成消息。订阅主题[i]的每个任务都将收到通知并开始工作。
答案 4 :(得分:4)
你的问题很有趣,因为有人可以使用这种设计模拟一个简单的神经网络。就答案而言,我希望将问题视为任务排序而不是多线程/并发问题,因为并发可以通过执行有序任务来实现。现在让我们尝试使用event driven programming来实现它,因为它允许很好的松耦合组件。所以,现在我们的设计本质上是反应性的,所以一旦完成我们就会担心发出相关任务的信号,让我们使用observer pattern here。
你的任务既是可观察的,也是观察者,因为他们等待前任的通知并通知后继,这给了我们以下的构造。
// Task.java
public abstract class Task extends Observable implements Runnable, Observer {
private final Mutex lock = new Mutex();
private final String taskId;
public String getTaskId() {
return this.taskId;
}
private final Set<String> completedTasks;
private final Set<String> shouldCompletedTasksBeforeStart;
public Task(final String taskId) {
this.taskId = taskId;
this.completedTasks = new HashSet<>();
this.shouldCompletedTasksBeforeStart = new HashSet<>();
}
@Override
public void run() {
while (true) {
this.lock.getLock();
if (this.completedTasks.equals(this.shouldCompletedTasksBeforeStart)) {
doWork();
setChanged();
notifyObservers(this.taskId);
// reset
this.completedTasks.clear();
}
this.lock.freeLock();
try {
// just some sleep, you change to how it fits you
Thread.sleep(1000);
} catch (final InterruptedException e) {
// TODO Auto-generated catch block
}
}
}
@Override
public void update(final Observable observable, final Object arg) {
this.lock.getLock();
this.completedTasks.add((String) arg);
this.lock.freeLock();
}
public void addPredecessorTask(final Task task) {
if (this.taskId.equals(task.taskId)) {
return;
}
this.lock.getLock();
// Notice here, it is a little logic make your predecessor/successor work
task.addObserver(this);
this.shouldCompletedTasksBeforeStart.add(task.taskId);
this.lock.freeLock();
}
protected abstract void doWork();
}
//HelloTask.java
public static class HelloTask extends Task {
public HelloTask(final String taskId) {
super(taskId);
}
@Override
protected void doWork() {
System.out.println("Hello from " + getTaskId() + "!");
}
}
//Main.java
public class Main {
public static void main(final String[] args) {
final HelloTask helloTaskA = new HelloTask("A");
final HelloTask helloTaskB = new HelloTask("B");
final HelloTask helloTaskC = new HelloTask("C");
helloTaskA.addPredecessorTask(helloTaskB);
helloTaskC.addPredecessorTask(helloTaskB);
final ExecutorService pool = Executors.newFixedThreadPool(10);
pool.execute(helloTaskC);
pool.execute(helloTaskA);
pool.execute(helloTaskB);
}
}
实施是非常基础的,你可以改进它,但它为你提供了基础结构。知道你在哪里应用它会很有趣吗?
答案 5 :(得分:3)
如果我很了解您的需求,您可以使用像Activity这样的工作流引擎来解决它。 我认为这比根据您的特定需求重新创建工作流引擎更容易。
答案 6 :(得分:3)
如果你想重新发明轮子并自己开发解决方案,那很好 - 这是你的选择。但是,正确地执行此操作相当困难,尤其是线程部分。但是,如果您可以考虑至少使用构建基块的一些外部帮助,那么可以是:
ListenableFuture
- 使用此库,您可以创建Callable
并将其提供给特殊线程池执行程序,然后允许ListenableFuture
完成自定义回调。Observable
,它允许混合和匹配不同的任务。这使用非命令式编码风格,所以要小心!答案 7 :(得分:3)
您的问题似乎是观察者模式的修改版本。 下面的解决方案更为通用,因为它允许继续依赖任务列表。
创建一个类Task,如下所示:
class Task{
List<Task> partialCompletionSuccessors;//List of tasks dependent on the partial completeion of this Task
List<Task> fullCompletetionSuccessors;//List of tasks dependent on the full completeion of this Task
List<Task> partialCompletionPredeccessor;//List of tasks that this task depends on their partial completion to start
List<Task> fullCompletetionPredeccessor;//List of tasks that this task depends on their full completion to start
private void notifySuccessorsOfPartialCompletion(){
for(Task task: partialCompletionSuccessors){
task.notifyOfPartialCompletion(this);
}
}
private void notifySuccessorsOfFullCompletion(){
for(Task task: fullCompletetionSuccessors){
task.notifyOfPartialCompletion(this);
}
}
private tryToProcceed(){
if(partialCompletionPredeccessor.size() == 0 && fullCompletetionPredeccessor.size()==0 ){
//Start the following task...
....
//When this task partially completes
notifySuccessorsOfPartialCompletion();
//When this task fully completes
notifySuccessorsOfFullCompletion();
}
}
public void notifyOfPartialCompletion(Task task){// A method to notify the following task that a predeccessor task has partially completed
partialCompletionPredeccessor.remove(task);
tryToProcceed();
}
public void notifyOfFullCompletion(Task task){// A method to notify the following task that a predeccessor task has partially completed
fullCompletetionPredeccessor.remove(task);
tryToProcceed();
}
}
答案 8 :(得分:1)
这个问题很重要。我可以在您的设计中看到至少三个不同的子系统:
您的描述非常高,因此您可以开始设计这三个部分所需的抽象以及它们如何相互交互(就接口而言)。
一旦掌握了所有这些,就可以开始提供一些简单的实现。我将从一个&#34;本地模式&#34;开始,使用一个简单的内存DAG,一个阻塞队列和一些类型的java执行器。
您的问题未提供有关SLA,作业长度,失败/重试政策,交易等的详细信息,因此很难说明您的模块应如何实施。但我建议从高级抽象的角度思考并迭代实现。伟大的代码永远不会修复糟糕的设计。
如果需要,您可以停在那里,或者开始用第三方产品替换每个实施。
答案 9 :(得分:1)
有一个专门用于此目的的框架,称为Dexecutor,使用Dexecutor,您可以根据图形来模拟您的需求,在执行时,dexecutor将负责以可靠的方式执行它。
例如:
@Test
public void testDependentTaskExecution() {
ExecutorService executorService = newExecutor();
ExecutionEngine<Integer, Integer> executionEngine = new DefaultExecutionEngine<>(executorService);
try {
DefaultDependentTasksExecutor<Integer, Integer> executor = new DefaultDependentTasksExecutor<Integer, Integer>(
executionEngine, new SleepyTaskProvider());
executor.addDependency(1, 2);
executor.addDependency(1, 2);
executor.addDependency(1, 3);
executor.addDependency(3, 4);
executor.addDependency(3, 5);
executor.addDependency(3, 6);
executor.addDependency(2, 7);
executor.addDependency(2, 9);
executor.addDependency(2, 8);
executor.addDependency(9, 10);
executor.addDependency(12, 13);
executor.addDependency(13, 4);
executor.addDependency(13, 14);
executor.addIndependent(11);
executor.execute(ExecutionConfig.NON_TERMINATING);
Collection<Node<Integer, Integer>> processedNodesOrder = Deencapsulation.getField(executor, "processedNodes");
assertThat(processedNodesOrder).containsAll(executionOrderExpectedResult());
assertThat(processedNodesOrder).size().isEqualTo(14);
} finally {
try {
executorService.shutdownNow();
executorService.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
}
}
private Collection<Node<Integer, Integer>> executionOrderExpectedResult() {
List<Node<Integer, Integer>> result = new ArrayList<Node<Integer, Integer>>();
result.add(new Node<Integer, Integer>(1));
result.add(new Node<Integer, Integer>(2));
result.add(new Node<Integer, Integer>(7));
result.add(new Node<Integer, Integer>(9));
result.add(new Node<Integer, Integer>(10));
result.add(new Node<Integer, Integer>(8));
result.add(new Node<Integer, Integer>(11));
result.add(new Node<Integer, Integer>(12));
result.add(new Node<Integer, Integer>(3));
result.add(new Node<Integer, Integer>(13));
result.add(new Node<Integer, Integer>(5));
result.add(new Node<Integer, Integer>(6));
result.add(new Node<Integer, Integer>(4));
result.add(new Node<Integer, Integer>(14));
return result;
}
private ExecutorService newExecutor() {
return Executors.newFixedThreadPool(ThreadPoolUtil.ioIntesivePoolSize());
}
private static class SleepyTaskProvider implements TaskProvider<Integer, Integer> {
public Task<Integer, Integer> provideTask(final Integer id) {
return new Task<Integer, Integer>() {
private static final long serialVersionUID = 1L;
public Integer execute() {
if (id == 2) {
throw new IllegalArgumentException("Invalid task");
}
return id;
}
};
}
}
这是建模图
这意味着任务#1,12和11将并行运行,一旦其中一个完成其依赖任务就会启动,例如一旦任务#1完成,其依赖任务#2和#3将启动
答案 10 :(得分:0)
从多线程中提取
class TaskA{
SimpleTask Y;
SimpleTask Z;
SimpleTask PJ;
SimpleTask RJ;
Run(){
// do the partial job
PJ.Run();
Y.Run();
// do the remaining job
RJ.Run();
Z.Run();
// return;
}
}
class TaskB{
TaskA P;
SimpleTask J;
Run(){
// do the job
J.Run();
P.Run();
// return;
}
}