以下类别的线程安全是否会被打破;我确信它不可能但只是想要倍加肯定,因为它不容易测试

时间:2011-06-07 19:28:54

标签: java multithreading concurrency hashset

public class ThreadSafe implements ITaskCompletionListener {

private final Set<String> taskIds = new HashSet<String>();
private final Set<String> successfulIds = new HashSet<String>();
private final Set<String> cancelledIds = new HashSet<String>();
private final Set<String> errorIds = new HashSet<String>();

public ThreadSafe() {

}

// invoked concurrently
@Override
public void onCancel(String pTaskId) {
    remove(pTaskId);
    cancelledIds.add(pTaskId);
}

// invoked concurrently
@Override
public void onError(String pTaskId) {
    remove(pTaskId);
    errorIds.add(pTaskId);
}

// invoked concurrently
@Override
public void onSuccess(String pTaskId) {
    remove(pTaskId);
    successfulIds.add(pTaskId);
}

private void remove(String pTaskId) {
    taskIds.remove(pTaskId);
}

}

7 个答案:

答案 0 :(得分:8)

来自HashSet文档:

  

请注意,此实施未同步。如果多个线程同时访问哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步

所以不,你的代码不是线程安全的。同时访问任何方法可能会产生奇怪的结果。

答案 1 :(得分:4)

您可以使用一个线程安全的集合,而不是在集合之间拥有大量集合和传递ID。

private final ConcurrentMap<String, State> idState = new ConcurrentHashMap<String, State>();
enum State { TASK, SUCCESS, CANCELLED, ERROR }

public void onSuccess(String taskId) {
    idState.put(taskId, State.SUCCESS);
}

public void onCancelled(String taskId) {
    idState.put(taskId, State.CANCELLED);
}

public void onError(String taskId) {
    idState.put(taskId, State.ERROR);
}

public void remove(String taskId) {
    idState.remove(taskId);
}

答案 2 :(得分:1)

方法onErroronSuccessonCancel可以由两个或多个线程并行调用,最终可以并行调用taskIds.remove,这不是线程 - 安全

只需将这三种方法标记为synchronized即可完成。

答案 3 :(得分:1)

彼得是对的。 “而不是在集合之间有很多集合和传递id,你可以使用一个线程安全的集合。”

这会使它变得不那么复杂,让它看起来不容易打破。

答案 4 :(得分:1)

这段代码充满了线程安全问题。

HashSet不是线程安全的(它基于非线程安全的HashMap - 竞争条件,最终导致infinite loops)。

其次,taskIds集合中的ID与其他集合中的ID之间没有原子性,因此任务的存在是短暂的。

第三,代码隐含地假设任务状态只是inProgress - &gt;成功|错误|取消,并且没有并发任务执行。如果不是这样,则代码失败。

答案 5 :(得分:0)

我认为你应该在同时调用的方法中使用同步块。

示例

public void onCancel(String pTaskId) {
synchronized(this){
    remove(pTaskId);
    cancelledIds.add(pTaskId);
}
}

答案 6 :(得分:0)

感谢你们每个人的回复,也指出我粘贴的代码中明显的bloopers。我将介绍我正在解决的问题。

我将收到请求(每个请求都有一组任务ID);我有一个线程池,用于提交此请求(每个请求的批处理任务)。我将从客户端接受将在任务完成后调用的侦听器。在当前上下文中,我可以使用ExecutorCompletionService等待与请求完成相对应的所有任务,但是,我不希望进行任何阻塞调用。

相反,我希望子类FutureTask(覆盖done()方法),这将在我的子类executor服务的newTaskFor()中实例化。与一个请求相对应的所有未来任务将共享TaskCompletionListener,并且覆盖done()方法将调用success()error()之一或取消。 TaskCompletionListener将使用初始任务ID集(任务数)进行初始化,并且对于每次调用,将计数减1,之后将发出完成信号。

这是代码的修改版本,我希望这次我没有犯过很多错误(是的,再次感谢上次所有答案)。

public class TaskCompletionListener implements ITaskCompletionListener {

private final AtomicInteger taskCount;

private final IProcessCompletionListener completionListener;

public TaskCompletionListener(final HashSet<String> pTaskIds, IProcessCompletionListener pCompletionListener) {
    taskCount = new AtomicInteger(pTaskIds.size());
    completionListener = pCompletionListener;
}

// invoked concurrently
@Override
public void onCancel(String pTaskId) {
    checkCompletion();
}

// invoked concurrently
@Override
public void onError(String pTaskId) {
    checkCompletion();
}

// invoked concurrently
@Override
public void onSuccess(String pTaskId) {
    checkCompletion();
}

private void checkCompletion() {
              // method level confinement  
    int currentCount = taskCount.decrementAndGet();
    if (currentCount == 0) {
        completionListener.onProcessComplete();
    }
}

interface IProcessCompletionListener {
    void onProcessComplete();
}
}