0

Suppose I have a program that keeps a list of elements and I want to be able to have a arbitrary number of threads executing jobs and to keep the program threadsafe.

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);
        }
    }

So would it be possible to keep locks per index on the ArrayList and when the different threads want to access objects in the list they will only have to wait for locks on specific elements in the array?

6
  • Quick advice: does this help? stackoverflow.com/questions/46436946/… Commented Feb 7, 2020 at 9:02
  • 2
    What are you actually trying to ensure by locking the elements? Your readFromList element gets the item from the list and then immediately releases the lock; so, the only thing I can imagine you hoping for here is visibility of the element up to the time it was last written with writeToList. Commented Feb 7, 2020 at 9:11
  • @AndyTurner the lock in 'readFromList' is just to make sure a thread does not read a value while it is being written to. Commented Feb 7, 2020 at 9:43
  • 1
    list(i) = obj; is not valid syntax Commented Feb 7, 2020 at 13:17
  • 1
    @AndyTurner …and the visibility is not guaranteed with this construct. Commented Feb 7, 2020 at 14:44

1 Answer 1

4

synchronized does not operate on variables but objects. It also doesn’t lock memory, but only provides mutual exclusion and memory visibility guarantees for threads performing synchronized on the same object.

This implies that any synchronization on a mutable variable doesn’t work at all.

For your specific case, you’d first have to ensure that the ArrayList is pre-filled with the necessary number of elements before the multi-threaded access even starts and never changes its size, as otherwise, even the memory visibility of its internal array reference and size fields are not guaranteed.

Then, the general issues with synchronizing on a mutable variable arise.

A code like

synchronized(variable) {
    return variable;
}

bears two reads of variable, the first one being performed before entering the synchronized block, to determine, which object to synchronize on. Since this first read is performed without any synchronization primitive, it is not guaranteed to see the most recent value. Though, “most recent” doesn’t have much meaning anyway, as the writers performing

synchronized(variable) {
    variable = newValue;
}

are synchronizing on different objects and hence, have no ordering relation anyway.

But even the most trivial scenario, one writer performing synchronized(variable) { variable = newValue; }, followed by one reader performing synchronized(variable) { return variable; } does not work, as the writer synchronized on the old value, while the reader may read any value in the first access, including the most recent value. Since both threads synchronize on different objects, there are no guarantees, which makes the reader’s second read unspecified. As unintuitive as it is, the second read could even read an older value.

But this is not the worst outcome. When you are not using immutable objects like String or Integer, this racy read may return a more recent object reference, but does not guaranty the visibility of its member fields, ending up in an entirely inconsistent state.

In other words, your issues are not about the mutual exclusion covering only reading and writing a single variable, but the missing memory visibility guarantees.

When you are fine with a fixed size, which your example using only List.set and List.get implies, you can use an 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);
    }
}

If you need a changeable size or really need to lock a place for a nontrivial operation, you could use, e.g.

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
        });
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.