1

I have read some questions & answers on visibility of Java array elements from multiple threads, but I still can't really wrap my head around some cases. To demonstrate what I'm having trouble with, I have come up with a simple scenario: Assume that I have a simple collection that adds elements into one of its n buckets by hashing them into one (Bucket is like a list of some sort). And each bucket is separately synchronized. E.g. :

private final Object[] locks = new Object[10];
private final Bucket[] buckets = new Bucket[10];

Here a bucket i is supposed to be guarded by lock[i]. Here is how add elements code looks like:

public void add(Object element) {
        int bucketNum = calculateBucket(element); //hashes element into a bucket
        synchronized (locks[bucketNum]) {
            buckets[bucketNum].add(element);
        }
    }

Since 'buckets' is final, this would not have any visibility problem even without synchronization. My guess is, with synchronization, this wouldn't have any visibility problems without the final either, is this correct?

And finally, a bit trickier part. Assume I want to copy out and merge the contents of all buckets and empty the whole data structure, from an arbitrary thread, like this:

public List<Bucket> clear() {
    List<Bucket> allBuckets = new List<>();
    for(int bucketNum = 0; bucketNum < buckets.length; bucketNum++) {
        synchronized (locks[bucketNum]) {
            allBuckets.add(buckets[bucketNum]);
            buckets[bucketNum] = new Bucket();
        }    
    }
    return allBuckets;
}

I basically swap the old bucket with a newly created one and return the old one. This case is different from the add() one because we are not modifying the object referred by the reference in the array but we are directly changing the array/reference.

Note that I do not care if bucket 2 is modified while I'm holding the lock for bucket 1, I don't need the structure to be fully synchronized and consistent, just visibility and near consistency is enough.

So assuming every bucket[i] is only ever modified under lock[i], would you say that this code works? I hope to be able to learn why and why nots and have a better grasp of visibility, thanks.

8
  • 2
    Unrelated: read about raw generic types, and why you shouldn't be using them. (lists should have a type parameter!) Commented Sep 20, 2019 at 10:54
  • Your synchronization looks unusual to me and I share the dislike for raw type usage (as @GostCat statet). But it can work this way. You should make sure that none of the array elements is null, ever. But I really don't get what you mean by 'Visibility'. It seems an unrelated term here. Visibility is about public, private or package local (neither public nor private). And final has nothing to do with it. Commented Sep 20, 2019 at 11:01
  • @HooNose, by visibility the OP means 'Memory visibility' Commented Sep 20, 2019 at 11:21
  • @GhostCat hi; I left the generics out just to keep it more concise, consider it pseudo code. Commented Sep 20, 2019 at 13:08
  • Re, "Since 'lists' is final,..." Did you mean to say, "Since buckets is final?" There is no variable named lists in your example code. Commented Sep 20, 2019 at 14:07

2 Answers 2

2

First question.

Thread safety in this case depends on whether the reference to the object containing locks and buckets (let's call it Container) is properly shared.

Just imagine: one thread is busy instantiating a new Container object (allocating memory, instantiating arrays, etc.), while another thread starts using this half-instantiated object where locks and buckets are still null (they have not been instantiated by the first thread yet). In this case this code:

    synchronized (locks[bucketNum]) {

becomes broken and throws NullPointerException. The final keyword prevents this and guarantees that by the time the reference to Container is not null, its final fields have already been initialized:

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields. (JLS 17.5)

Second question.

Assuming that locks and buckets fields are final and you don't care about consistency of the whole array and "every bucket[i] is only ever modified under lock[i]", this code is fine.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, the instantiation - publishing case is noted.
0

Just to add to Pavel's answer:

In your first question you ask

Since 'buckets' is final, this would not have any visibility problem even without synchronization. My guess is, with synchronization, this wouldn't have any visibility problems without the final either, is this correct?

I'm not sure what you mean by 'visibility problems', but for sure, without the synchronized this code would be incorrect, if multiple threads would access buckets[i] with one of them modifying it (e.g. writing to it). There would be no guarantee that what one thread have written, becomes visible to another one. This also involves internal structures of the bucket which might be modified by the call to add.

Remember that final on buckets pertain only to the single reference to the array itself, not to its cells.

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.