1

我已经定义了一个 Synchronized ArrayList,它将被多线程访问。

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

我想为上面的列表定义一些自定义操作,如add, remove, edit, 。delete

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

谢谢!

4

3 回答 3

2

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

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

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

于 2013-04-25T05:21:29.217 回答
1

问的问题没有意义。您问的是向对象添加方法,而不是向类添加方法——您不能在 Java 中这样做。Collections.synchronizedList选择 List 的类——它是java.util.Collections.SynchronizedRandomAccessListor ...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> {
    ...

您现在的问题是:我们与覆盖的方法有什么关系?答案是……这取决于!

要记住的一般规则是,被覆盖的方法不会继承其 super 的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);
}

在这种情况下,您实际上不需要 synchronize 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;
}

这里我们添加了额外的状态,所以我们必须同步它!请注意,在这种特殊情况下,您也可以设置lastAddedvolatile 并且不使任一方法同步——但在其他情况下,这可能是不可能的。

场景4:调用不止一种方法super

例子:

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

假设您关心原子性,那么您确实需要同步。它不是继承自super.add. indexOf方法 (和) 中的每个调用super.add都是同步的,但您需要额外的原子性。

(更准确地说,您需要在监视器上同步时调用这两个方法,这也应该是每个方法使用的监视器。在这种情况下,这两个方法是同步的,因此监视器只是this,这是同步方法同步的内容。)

最后的想法

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

我没有在上面介绍所有可能的情况,但希望这能让你了解事情是如何运作的。

于 2013-04-25T07:42:22.207 回答
1

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

于 2013-04-25T08:16:02.153 回答