Java 如何终止在多线程中超时的任务?

Java 如何终止在多线程中超时的任务?,java,multithreading,executorservice,callable,futuretask,Java,Multithreading,Executorservice,Callable,Futuretask,我需要创建一个库,其中包含同步和异步方法 executeSynchronous()-等待得到结果,然后返回结果 executeAsynchronous()-立即返回一个Future,如果需要,可以在其他事情完成后处理 我的库的核心逻辑 客户将使用我们的库,他们将通过传递DataKeybuilder对象来调用它。然后,我们将使用该DataKey对象构造一个URL,并通过执行它对该URL进行HTTP客户端调用,在我们将响应作为JSON字符串返回后,我们将通过创建DataResponse对象将JS

我需要创建一个库,其中包含同步和异步方法

  • executeSynchronous()
    -等待得到结果,然后返回结果
  • executeAsynchronous()
    -立即返回一个Future,如果需要,可以在其他事情完成后处理
我的库的核心逻辑

客户将使用我们的库,他们将通过传递
DataKey
builder对象来调用它。然后,我们将使用该
DataKey
对象构造一个URL,并通过执行它对该URL进行HTTP客户端调用,在我们将响应作为JSON字符串返回后,我们将通过创建
DataResponse
对象将JSON字符串原样发送给我们的客户。有些客户会调用
executeSynchronous()
,有些客户可能会调用
executeSynchronous()
,因此我需要在库中分别提供两种方法

接口:

public interface Client {

    // for synchronous
    public DataResponse executeSynchronous(DataKey key);

    // for asynchronous
    public Future<DataResponse> executeAsynchronous(DataKey key);
}
将执行实际任务的简单类:

public class Task implements Callable<DataResponse> {

    private DataKey key;
    private RestTemplate restTemplate;

    public Task(DataKey key, RestTemplate restTemplate) {
        this.key = key;
        this.restTemplate = restTemplate;
    }

    @Override
    public DataResponse call() {
        DataResponse dataResponse = null;
        String response = null;

        try {
            String url = createURL();
            response = restTemplate.getForObject(url, String.class);

            // it is a successful response
            dataResponse = new DataResponse(response, DataErrorEnum.NONE, DataStatusEnum.SUCCESS);
        } catch (RestClientException ex) {
            PotoLogging.logErrors(ex, DataErrorEnum.SERVER_DOWN, key);
            dataResponse = new DataResponse(null, DataErrorEnum.SERVER_DOWN, DataStatusEnum.ERROR);
        } catch (Exception ex) {
            PotoLogging.logErrors(ex, DataErrorEnum.CLIENT_ERROR, key);
            dataResponse = new DataResponse(null, DataErrorEnum.CLIENT_ERROR, DataStatusEnum.ERROR);
        }

        return dataResponse;
    }

    // create a URL by using key object
    private String createURL() {
        String url = somecode;
        return url;
    }
}
但是我想确定一下,我在
executeSynchronous
方法中取消有时间的任务的方式是否正确

由于我正在调用
Future
上的
cancel()
,如果任务仍在队列中,它将停止运行,因此我不确定我所做的是否正确?这样做的正确方法是什么


如果有更好的方法,那么有人能提供一个例子吗?

如果任务仍在队列中,则只需调用
future.cancel()
即可取消它,但显然您不知道该任务是否在队列中。此外,即使您要求
future
中断任务,它也可能不起作用,因为您的任务仍可能在执行忽略线程中断状态的操作

因此,您可以使用
future.cancel(true)
,但您需要确保您的任务(线程)确实考虑到线程中断状态。例如,正如您所提到的,您进行http调用,因此您可能需要在线程中断时立即关闭http客户端资源

请参考下面的例子

我已尝试实现任务取消场景。通常,线程可以检查
isInterrupted()
,并尝试终止自身。但是,当您使用线程池执行器时,如果任务不是真正像
而(!thread.isInterrupted()){//execute task}
,这将变得更加复杂

在这个例子中,一个任务正在编写一个文件(我没有使用http调用来保持它的简单性)。线程池执行器开始运行任务,但调用方希望在100毫秒后取消该任务。现在future向线程发送中断信号,但可调用任务在写入文件时无法立即检查。因此,为了实现这一点,callable维护了一个它将要使用的IO资源列表,并且一旦future想要取消任务,它只需在所有IO资源上调用
cancel()
,它会使用IOException终止任务,然后线程完成

public class CancellableTaskTest {

    public static void main(String[] args) throws Exception {
        CancellableThreadPoolExecutor threadPoolExecutor = new CancellableThreadPoolExecutor(0, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
        long startTime = System.currentTimeMillis();
        Future<String> future = threadPoolExecutor.submit(new CancellableTask());
        while (System.currentTimeMillis() - startTime < 100) {
            Thread.sleep(10);
        }
        System.out.println("Trying to cancel task");
        future.cancel(true);
    }
}

class CancellableThreadPoolExecutor extends ThreadPoolExecutor {

    public CancellableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new CancellableFutureTask<T>(callable);
    }
}

class CancellableFutureTask<V> extends FutureTask<V> {

    private WeakReference<CancellableTask> weakReference;

    public CancellableFutureTask(Callable<V> callable) {
        super(callable);
        if (callable instanceof CancellableTask) {
            this.weakReference = new WeakReference<CancellableTask>((CancellableTask) callable);
        }
    }

    public boolean cancel(boolean mayInterruptIfRunning) {
        boolean result = super.cancel(mayInterruptIfRunning);
        if (weakReference != null) {
            CancellableTask task = weakReference.get();
            if (task != null) {
                try {
                    task.cancel();
                } catch (Exception e) {
                    e.printStackTrace();
                    result = false;
                }
            }
        }
        return result;
    }
}

class CancellableTask implements Callable<String> {

    private volatile boolean cancelled;
    private final Object lock = new Object();
    private LinkedList<Object> cancellableResources = new LinkedList<Object>();

    @Override
    public String call() throws Exception {
        if (!cancelled) {
            System.out.println("Task started");
            // write file
            File file = File.createTempFile("testfile", ".txt");
            BufferedWriter writer = new BufferedWriter(new FileWriter(file));
            synchronized (lock) {
                cancellableResources.add(writer);
            }
            try {
                long lineCount = 0;
                while (lineCount++ < 100000000) {
                    writer.write("This is a test text at line: " + lineCount);
                    writer.newLine();
                }
                System.out.println("Task completed");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                writer.close();
                file.delete();
                synchronized (lock) {
                    cancellableResources.clear();
                }
            }
        }
        return "done";
    }

    public void cancel() throws Exception {
        cancelled = true;
        Thread.sleep(1000);
        boolean success = false;
        synchronized (lock) {
            for (Object cancellableResource : cancellableResources) {
                if (cancellableResource instanceof Closeable) {
                    ((Closeable) cancellableResource).close();
                    success = true;
                }
            }
        }
        System.out.println("Task " + (success ? "cancelled" : "could not be cancelled. It might have completed or not started at all"));
    }
}
确保传递的可取消资源列表与您在
cancelabletask
中维护的相同

现在您需要像这样修改
cancelabletask
中的
cancel()
方法-

public class CancellableSimpleClientHttpRequestFactory extends SimpleClientHttpRequestFactory {

    private List<Object> cancellableResources;

    public CancellableSimpleClientHttpRequestFactory() {
    }

    public CancellableSimpleClientHttpRequestFactory(List<Object> cancellableResources) {
        this.cancellableResources = cancellableResources;
    }

    protected HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException {
        HttpURLConnection connection = super.openConnection(url, proxy);
        if (cancellableResources != null) {
            cancellableResources.add(connection);
        }
        return connection;
    }
}
synchronized (lock) {
    for (Object cancellableResource : cancellableResources) {
        if (cancellableResource instanceof HttpURLConnection) {
            ((HttpURLConnection) cancellableResource).disconnect();
            success = true;
        }
    }
}

谢谢你的建议。当你说
如果你要求future中断任务
,这意味着我正在调用future的cancel,所以它会中断我的任务?这就是你的意思?告诉我这个-如果任务得到了timedout,那么我对该任务感兴趣,这样我就可以安全地终止该任务。对吗?我猜
future.cancel
只返回布尔值,对吗?它将抛出任何异常?此外,线程中断后是否必须立即关闭http客户端资源?如果是,那么我需要在哪里做呢?当您调用
future.cancel(true)
时,它将在内部调用执行任务的线程的中断方法。现在,一旦线程被中断,任务就有责任停止其执行。这还取决于正在执行的任务的性质,以使线程能够检查其自身的中断状态。类似于
while(!thread.isInterrupted()){//execute task}
。在我的回答中添加了一个示例。请注意,您的用例是不同的,因此您可能必须以与我解释的稍有不同的方式来实现它,但您应该或多或少了解如何终止线程的方向。
public class CancellableSimpleClientHttpRequestFactory extends SimpleClientHttpRequestFactory {

    private List<Object> cancellableResources;

    public CancellableSimpleClientHttpRequestFactory() {
    }

    public CancellableSimpleClientHttpRequestFactory(List<Object> cancellableResources) {
        this.cancellableResources = cancellableResources;
    }

    protected HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException {
        HttpURLConnection connection = super.openConnection(url, proxy);
        if (cancellableResources != null) {
            cancellableResources.add(connection);
        }
        return connection;
    }
}
    RestTemplate template = new RestTemplate(new CancellableSimpleClientHttpRequestFactory(this.cancellableResources));
synchronized (lock) {
    for (Object cancellableResource : cancellableResources) {
        if (cancellableResource instanceof HttpURLConnection) {
            ((HttpURLConnection) cancellableResource).disconnect();
            success = true;
        }
    }
}