【问题标题】:Is it possible to lock single elements by index in an ArrayList in Java?是否可以通过 Java ArrayList 中的索引锁定单个元素?
【发布时间】:2020-05-23 09:30:44
【问题描述】:

假设我有一个保存元素列表的程序,并且我希望能够让任意数量的线程执行作业并保持程序线程安全。

class Program() {

    List<Object> list = new ArrayList<Object>();

    readFromList(int index) {
        synchronized(list.get(index)) {   //If i exclude this lock can other threads access the element 
            Object tmp = list.get(index); //if the lock in writeToList() is active?
            return tmp;
        }
    }

    writeToList(int index, Object obj) {
        synchronized(list.get(index)) {
            list.set(obj);
        }
    }

那么是否有可能在ArrayList 上保留每个索引的锁,并且当不同的线程想要访问列表中的对象时,它们只需要等待数组中特定元素上的锁吗?

【问题讨论】:

  • 快速建议:这有帮助吗? stackoverflow.com/questions/46436946/…
  • 您实际上是通过锁定元素来确保什么?您的readFromList 元素从列表中获取项目,然后立即释放锁;所以,我能想象你在这里唯一希望的是直到上次使用writeToList 编写元素时的可见性。
  • @AndyTurner 'readFromList' 中的锁只是为了确保线程在写入时不会读取值。
  • list(i) = obj; 语法无效
  • @AndyTurner ...并且此构造无法保证可见性。

标签: java multithreading concurrency thread-safety locking


【解决方案1】:

synchronized 不会对变量进行操作,而是对对象进行操作。它也不锁定内存,而只为在同一对象上执行synchronized 的线程提供互斥和内存可见性保证。

这意味着对可变变量的任何同步根本不起作用。

对于您的具体情况,您首先必须确保在多线程访问开始之前,ArrayList 已预先填充了必要数量的元素,并且永远不会更改其大小,否则,即使是内存不保证其内部数组引用和大小字段的可见性。

然后,出现在可变变量上同步的一般问题。

类似的代码

synchronized(variable) {
    return variable;
}

variable 进行两次读取,第一次在进入synchronized 块之前执行,以确定要同步哪个对象。由于第一次读取是在没有任何同步原语的情况下执行的,因此不能保证看到最新的值。不过,无论如何,“最近的”并没有多大意义,因为作家们正在表演

synchronized(variable) {
    variable = newValue;
}

在不同的对象上同步,因此无论如何都没有排序关系。

但即使是最琐碎的场景,一位编写者执行 synchronized(variable) { variable = newValue; },然后一位读取者执行 synchronized(variable) { return variable; } 也不起作用,因为编写者 在旧值上同步,而读取器可能会读取首次访问中的任何值,包括最近的值。由于两个线程在不同的对象上同步,因此无法保证,这使得读者的第二次读取未指定。尽管不直观,但第二次读取甚至可能读取较旧的值。

但这还不是最坏的结果。当您不使用 StringInteger 之类的不可变对象时,这种活泼的读取可能会返回更新的对象引用,但不能保证其成员字段的可见性,最终会处于完全不一致的状态。

换句话说,您的问题不在于只涉及读取和写入单个变量的互斥,而是缺少内存可见性保证。

当您可以使用固定大小时,您的示例仅使用 List.setList.get 暗示,您可以使用 AtomicReferenceArray

public class Program {
     // provide needed size
    final AtomicReferenceArray<Object> data = new AtomicReferenceArray<>(100);

    Object readFromList(int index) {
        return data.get(index);
    }

    // returns old object
    Object writeToList(int index, Object obj) {
        return data.getAndSet(index, obj);
    }
}

如果您需要可变大小或确实需要锁定一个位置以进行重要操作,您可以使用,例如

public class Program {
    final ConcurrentHashMap<Integer, Object> data = new ConcurrentHashMap<>();

    // returns the new index
    int addToList(Object o) {
        int ix;
        do ix = data.size(); while(data.putIfAbsent(ix, o) != null);
        return ix;
    }

    Object readFromList(int index) {
        return data.get(index);
    }

    void updateList(int index, Object input) {
        data.compute(index, (ix, old) -> {
            // compute based on old and input
            return input;// or computed value
        });
    }
}

【讨论】:

    猜你喜欢
    • 2012-08-06
    • 2022-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-19
    • 1970-01-01
    相关资源
    最近更新 更多