我应该同步我的方法吗?

时间:2013-04-25 05:10:56

标签: java multithreading list thread-safety

我已定义了一个可由多线程访问的Synchronized ArrayList。

List<Object> objList = Collections.synchronizedList(new ArrayList<Object>());

我想为上面的列表定义一些自定义操作,例如addremoveeditdelete

我的操作是否需要synchronized? 由于我已经制作了我的列表synchronizedList,这不足以确保原子操作吗?

谢谢!

3 个答案:

答案 0 :(得分:2)

如果您定义自定义操作,那些将从Collections.synchronizedList()继承同步(当前同步的方法将保持同步)。

可以这样想:如果你有一个线程安全类,然后你可以扩展,那么线程安全性也不会扩展到新类中的新方法。线程安全和同步仅适用于继承的方法,而不适用于自定义定义。

因此,是的,如果您希望同步新方法,则必须明确声明它们synchronized

答案 1 :(得分:1)

提出的问题没有意义。您要问的是将方法添加到对象而不是类中 - 您无法在Java中执行此操作。 Collections.synchronizedList选择了List的类 - 它是java.util.Collections.SynchronizedRandomAccessList...SynchronizedList。此外,每个都是包私有的,所以你不能扩展它们。

但是,假设您确实有类似于提供的实现:

public class SyncedList<E> implements List<E> {
    private final List<E> delegate;

    public SyncedList(List<E> delegate) { this.delegate = delegate; }

    @Override
    public synchronized boolean add(E e) {
        return delegate.add(e);
    }

    @Override
    public synchronized void clear() {
        delegate.clear();
    }

    // ... etc

现在你扩展这个类:

public class MySyncedList<E> extends SyncedList<E> {
    ...

现在你的问题是:我们如何处理覆盖方法?答案是......这取决于它!

要记住的一般规则是覆盖方法继承其超级synchronized修饰符。但你可能不需要它。以下是一些场景:

场景1:根本不调用super方法

示例:

@Override
public void clear() {
    throw new UnsupportedOperationException();
}

在这种情况下,应该清楚(双关语警!)您不需要任何同步,因为没有什么可以同步。 (琐碎的例子,但很容易让路。)

场景2:在不触及其他可变状态的情况下调用一个super方法

示例:

private final E dontAllow; // assume this is set in the constructor
@Override
public boolean add(E e) {
    if (Objects.equals(e, dontAllow))
        throw new IllegalArgumentException("can't add element: " + e);
    super.add(e);
}

在这种情况下,您实际上不需要同步add。对super.add(e)的调用将保持其同步,并且您没有触及任何其他可变状态,因此没有任何额外的同步。

场景3:调用一个super方法,但也触及可变状态

示例:

private E lastAdded = null;

@Override
public synchronized boolean add(E e) {
    boolean result = super.add(e); // do this first in case it throws an exception
    lastAdded = e;
    return result;
}

public synchronized E getLastAdded() {
    return lastAdded;
}

这里我们添加了其他状态,因此我们必须同步它!请注意,在这种特殊情况下,您可能也使lastAdded变为volatile并且没有使这两种方法同步 - 但在其他情况下,这可能是不可能的。

场景4:在super上多调用一个方法

示例:

public synchronized void add(E e) {
    if (indexOf(e) >= 0)
        return false;
    return super.add(e);
}

这里你需要同步,假设你关心原子性。它不是从super.add继承而来的。方法(indexOfsuper.add)中的每个调用都是同步的,但您需要额外的原子性。

(更精确一点,你需要在监视器上同步时调用这两个方法,这也应该是每个方法使用的监视器。在这种情况下,这两个方法是同步的,所以monitor只是this,这是同步方法同步的。)

最后的想法

即使您不必同步覆盖方法,如果它读取或写入状态也可能是个好主意(例如,上面的方案2,如果使用volatile lastAdded方法,则为3)。所有其他mutator / accessor方法都是同步的,因此如果没有一个方法,您的API看起来会很奇怪。人们会问,“嘿,不应该add同步?”你必须说服他们不需要。更重要的是,你实际上不会获得任何东西,因为当它到达super电话时,该方法最终会同步。

我没有涵盖上述所有可能的情况,但希望能让您了解事情的运作方式。

答案 2 :(得分:1)

看看java.util.concurrent.CopyOnWriteArrayList。在您不需要进行大量突变的情况下,它提供了开箱即用的实时同步。