【问题标题】:Java synchronization, thread safety, wrapped objects, and race conditionsJava 同步、线程安全、包装对象和竞争条件
【发布时间】:2013-08-14 17:40:37
【问题描述】:

我正在开发一个带有线程池的系统,该线程池将提交链式任务。每个任务都将运行,检查其结果,然后根据结果执行另一个任务。这是在同一个线程中完成的,因此它不会将作业重新提交回线程池。每个任务都会将其结果添加到一个对象,该对象包装一个集合并提供一些其他次要功能,然后将其传递给链中的下一个任务。一旦到达链的末端,结果就会在 Future.get() 中返回,并带有超时时间,并且可以分析完整的结果。

每个任务都将调用一个外部服务,即一个 SOAP 请求。如果任务挂起,它很可能正在等待并等待响应。在这种情况下,如果 get() 超时,我会捕获 TimeoutException 并取消(true) 未来。这意味着在任务线程中抛出 InterruptedException(因为它可能处于等待状态)并且可以被捕获。异常被包装在一个响应对象中并放置在 Collection 包装器中。

现在,如果超时,我似乎无法从 Future 对象中取回该集合。我正在关注原始任务(或任务包装器),如果 TimeoutException 被捕获并且 Future 被取消,则从原始任务中检索 Collection 包装器,它应该包含直到点的结果超时。

但是,我不知道这本身是线程安全的还是竞争条件。如果主线程在取消Future对象后立即尝试访问被包装的Collection,那么被包装的异常会在里面吗?它会在并发迭代和修改的尝试中结束吗?有没有办法确保包装的异常在主线程检索它之前进入该集合?

一些问答示例代码:

public class MyCallable implements Callable<MyResponseCollection> {

    private MyResponseCollection responses = new MyResponseCollection();  //Wraps a List
    private MyTask task; //This is set prior to submitting the task wrapper

    public MyResponseCollection call() throws Exception() {
        task.setCollection(responses);
        task.call(); //kicks off the task chain, not an override to Callable.call
        return responses; //If all goes well, this is passed into the Future
    }
}

public class MyTask {

    private MyResponseCollection responses;

    public void setCollection(MyResponseCollection responses){
        this.responses = responses;
    }

    public void call(){
        try{
            MyResponse m = this.doStuff();
            responses.add(m);
            this.executeNext(m); //Runs the next task based on the response, simplified here, responses object passed into the next task

        } catch (InterruptedException e){
            responses.add(new ExceptionResponse(e)); //Here's where we catch that potential interrupt
        }
     }
     public MyResponse doStuff() throws InterruptedException{
         //All work done here
     }
}

这是我对多线程的第一次重大尝试,所以我不太确定如何确保线程等之间的操作顺序,或者我是否在这里做一些愚蠢的事情并且有更好的解决方案。 MyResponseCollection 上的 synchronize() 块是否也适用于其中的列表?是否要求所述 List 也是 Collections.synchronizedList?

【问题讨论】:

    标签: java multithreading thread-safety race-condition synchronized


    【解决方案1】:

    为了让一个线程在资源被释放时向另一个线程指示,我会在资源中添加一个Semaphore isDone 字段。

    public class MyResponseCollection {
        public final Semaphore isDone = new Semaphore(0);
    }
    
    public class MyCallable implements Callable<MyResponseCollection> {
        ...
        public MyResponseCollection call() throws Exception() {
            task.setCollection(responses);
            task.call(); //kicks off the task chain, not an override to Callable.call
            responses.isDone.acquire();
            return responses; //If all goes well, this is passed into the Future
        }
    }
    
    
    public class MyTask {
        ...
    
        public void call(){
            try{
    
            } finally {
                responses.isDone.release();
            }
         }
     }
    

    responses.isDone.acquire() 将阻塞直到许可可用,isDone 初始化为零许可。 MyTask#call() 在其 finally 块中添加了一个许可,这将唤醒 MyCallable

    【讨论】:

    • 太棒了!这正是我所需要的!不过,我稍微修改了您的结果,并用 .acquire() 和 .release() 包围了 MyCallable 中的 task.call()。由于可以在单个调用中链接多个任务,因此最好将锁保留在那里,因为它会通过成功完成或捕获的异常返回来到达 .release()。每个 MyTask 中的 finally 块在释放它时似乎有点不稳定,因此主线程可以处理结果。
    • @user1017413 在 finally 块中还有其他内容吗?如果在调用 release 之前在块内引发异常,那么您会看到一些不稳定的情况 - 如果是这种情况,请将 release 调用移动到块的顶部。
    • 不,只是发布,没有别的。
    猜你喜欢
    • 1970-01-01
    • 2021-06-04
    • 2016-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-21
    • 2020-10-08
    相关资源
    最近更新 更多