Java同步、线程安全、包装对象和竞争条件

Java同步、线程安全、包装对象和竞争条件,java,multithreading,thread-safety,race-condition,synchronized,Java,Multithreading,Thread Safety,Race Condition,Synchronized,我正在开发一个带有线程池的系统,该线程池将提交链接任务。每个任务都将运行,检查其结果,然后根据结果执行另一个任务。这是在同一线程内完成的,因此不会将作业重新提交回线程池。每个任务都会将其结果添加到一个对象中,该对象包装一个集合并提供一些其他次要函数,然后将其传递到链中的下一个任务中。到达链的末尾后,将在将来返回结果。get()并超时,然后可以分析完整的结果 每个任务都将调用外部服务,即SOAP请求。如果任务挂起,它很可能在等待响应。在这种情况下,如果get()超时,我将捕获TimeoutExce

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

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

现在,如果将来的对象超时,我似乎无法从中取回该集合。我正在考虑保留原始任务(或任务的包装器),如果捕获了TimeoutException并且取消了将来的任务,则从原始任务中检索集合包装器,该包装器应该包含到超时点为止的结果

然而,我不知道这本身是线程安全还是竞争条件。如果主线程在取消未来对象后立即尝试访问包装的集合,那么包装的异常是否会在其中?它会不会以并发迭代和修改的尝试而告终?在主线程检索之前,是否有方法确保包装的异常进入该集合

一些Q&D示例代码:

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
     }
}
公共类MyCallable实现可调用{
private MyResponseCollection responses=new MyResponseCollection();//包装列表
private MyTask task;//这是在提交任务包装器之前设置的
公共MyResponseCollection调用()引发异常(){
task.setCollection(响应);
task.call();//启动任务链,而不是覆盖Callable.call
return responses;//如果一切顺利,这将传递到将来
}
}
公共类MyTask{
私人MyResponseCollection回应;
公共void集合(MyResponseCollection响应){
这一点。反应=反应;
}
公开作废通知(){
试一试{
MyResponse m=this.doStuff();
答复.增加(m);
this.executeNext(m);//根据传递到下一个任务中的响应(此处简化)responses对象运行下一个任务
}捕捉(中断异常e){
responses.add(newexceptionresponse(e));//这里是我们捕捉潜在中断的地方
}
}
public MyResponse doStuff()抛出InterruptedException{
//所有的工作都在这里完成
}
}

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

如果在释放资源时让一个线程向另一个线程指示,我会在资源中添加一个
信号量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();
        }
     }
 }
公共类MyResponseCollection{
公共最终信号量isDone=新信号量(0);
}
公共类MyCallable实现了Callable{
...
公共MyResponseCollection调用()引发异常(){
task.setCollection(响应);
task.call();//启动任务链,而不是覆盖Callable.call
responses.isDone.acquire();
return responses;//如果一切顺利,这将传递到将来
}
}
公共类MyTask{
...
公开作废通知(){
试一试{
}最后{
responses.isDone.release();
}
}
}

responses.isDone.acquire()
将被阻止,直到许可证可用,而
isDone
将以零许可证初始化
MyTask#call()
在其
finally
块中添加了一个许可证,它将唤醒
MyCallable

太棒了!这正是我需要的!不过,我已经稍微修改了您的结果,并用.acquire()和.release()包围了MyCallable中的task.call()。由于多个任务可以在单个调用中链接,因此最好将锁保持在那里,因为它将通过成功完成或捕获的异常返回来达到.release()。每个MyTask中的finally块在释放时似乎有点不稳定,因此主线程可以处理结果。@user1017413 finally块中还有其他内容吗?如果在调用
release
之前在块内抛出异常,则您将看到一些片状-如果是这种情况,则将
release
调用移动到块的顶部。不,只是释放,没有其他。