0

I've searched for this question and I only found answer for primitive type arrays.

Let's say I have a class called MyClass and I want to have an array of its objects in my another class.

class AnotherClass {
    [modifiers(?)] MyClass myObjects;

    void initFunction( ... ) {
         // some code
         myObjects = new MyClass[] { ... };
    }

    MyClass accessFunction(int index) {
        return myObjects[index];
    }
}

I read somewhere that declaring an array volatile does not give volatile access to its fields, but giving a new value of the array is safe. So, if I understand it well, if I give my array a volatile modifier in my example code, it would be (kinda?) safe. In case of I never change its values by the [] operator.

Or am I wrong? And what should I do if I want to change one of its value? Should I create a new instance of the array an replace the old value with the new in the initial assignment?

AtomicXYZArray is not an option because it is only good for a primitive type arrays. AtomicIntegerArray uses native code for get() and set(), so it didn't help me.

Edit 1: Collections.synchronizedList(...) can be a good alternative I think, but now I'm looking for arrays.

Edit 2: initFunction() is called from a different class. AtomicReferenceArray seems to be a good answer. I didn't know about it, up to now. (I'm still interested in that my example code would work with volatile modifier (before the array) with only this two function called from somewhere else.)


This is my first question. I hope I managed to reach the formal requirements. Thanks.

Community
  • 1
  • 1
Gerviba
  • 3
  • 2
  • 4
  • 1
    "because it is only good for a primitive type arrays" What's wrong with [`AtomicReferenceArray`](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicReferenceArray.html)? – Andy Turner Jan 29 '18 at 19:26
  • Please write proper Java code and add comments if you have inline code questions. Also it is not clear if `initFunction` is called from constructor or somewhere else. – tsolakp Jan 29 '18 at 19:29
  • Possible duplicate of [How to access an array thread safely in Java?](https://stackoverflow.com/questions/29153404/how-to-access-an-array-thread-safely-in-java) – Lord Why Jan 29 '18 at 19:31
  • @LordWhy I read this topic before I asked my question. It doesn't answer my question, but thanks. – Gerviba Jan 29 '18 at 19:53
  • If you're using Java 9, you can use [MethodHandles.arrayElementVarHandle​](https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/MethodHandles.html#arrayElementVarHandle-java.lang.Class-). – Bubletan Jan 29 '18 at 21:02

3 Answers3

0

Yes you are correct when you say that the volatile word will not fulfill your case, as it will protect the reference to the array and not its elements.

If you want both, Collections.synchronizedList(...) or synchronized collections is the easiest way to go.

Using modifiers like you are inclining to do is not the way to do this, as you will not affect the elements.

If you really, must, use and array like this one: new MyClass[]{ ... };

Then AnotherClass is the one that needs to take responsibility for its safety, you are probably looking for lower level synchronization here: synchronized key word and locks.

The synchonized key word is the easier and yuo may create blocks and method that lock in a object, or in the class instance by default.

In higher levels you can use Streams to perform a job for you. But in the end, I would suggest you use a synchronized version of an arraylist if you are already using arrays. and a volatile reference to it, if necessary. If you do not update the reference to your array after your class is created, you don't need volatile and you better make it final, if possible.

Victor
  • 3,520
  • 3
  • 38
  • 58
0

For your data to be thread-safe you want to ensure that there are no simultaneous:

  1. write/write operations
  2. read/write operations

by threads to the same object. This is known as the readers/writers problem. Note that it is perfectly fine for two threads to simultaneously read data at the same time from the same object.

You can enforce the above properties to a satisfiable level in normal circumstances by using the synchronized modifier (which acts as a lock on objects) and atomic constructs (which performs operations "instantaneously") in methods and for members. This essentially ensures that no two threads can access the same resource at the same time in a way that would lead to bad interleaving.

if I give my array a volatile modifier in my example code, it would be (kinda?) safe.

The volatile keyword will place the array reference in main memory and ensure that no thread can cache a local copy of it within their private memory, which helps with thread visibility although it won't guarantee thread safety by itself. Also the use of volatile should be used sparsely unless by experienced programmers as it may cause unintended effects on the program.

And what should I do if I want to change one of its value? Should I create a new instance of the array an replace the old value with the new in the initial assignment?

Create synchronized mutator methods for the mutable members of your class if they need to be changed or use the methods provided by atomic objects within your classes. This would be the simplest approach to changing your data without causing any unintended side-effects (for example, removing the object from the array whilst a thread is accessing the data in the object being removed).

Lord Why
  • 14
  • 6
0

Volatile does actually work in this case with one caveat: all the operations on MyClass may only read values.

Compared to all what you might read about what volatile does, it has one purpose in the JMM: creating a happens-before relationship. It only affects two kinds of operations:

  • volatile read (eg. accessing the field)
  • volatile write (eg. assignment to the field)

That's it. A happens-before relationship, straight from the JLS §17.4.5:

  • Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.
  • A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
  • If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

These relationships are transitive. Taken all together this implies some important points: All actions taken on a single thread happened-before that thread's volatile write to that field (third point above). A volatile write of a field happens-before a read of that field (point two). So any other thread that reads the volatile field would see all the updates, including all referred to objects like array elements in this case, as visible (first point). Importantly, they are only guaranteed to see the updates visible when the field was written. This means that if you fully construct an object, and then assign it to a volatile field and then never mutate it or any of the objects it refers to, it will be never be in an inconsistent state. This is safe taken with the caveat above:

class AnotherClass {
    private volatile MyClass[] myObjects = null;

    void initFunction( ... ) {
         // Using a volatile write with a fully constructed object.
         myObjects = new MyClass[] { ... };
    }

    MyClass accessFunction(int index) {
        // volatile read
        MyClass[] local = myObjects;
        if (local == null) {
            return null; // or something else
        }
        else {
            // should probably check length too
            return local[index];
        }
    }
}

I'm assuming you're only calling initFunction once. Even if you did call it more than once you would just clobber the values there, it wouldn't ever be in an inconsistent state.

You're also correct that updating this structure is not quite straightforward because you aren't allowed to mutate the array. Copy and replace, as you stated is common. Assuming that only one thread will be updating the values you can simply grab a reference to the current array, copy the values into a new array, and then re-assign the newly constructed value back to the volatile reference. Example:

private void add(MyClass newClass) {
    // volatile read
    MyClass[] local = myObjects;
    if (local == null) {
        // volatile write
        myObjects = new MyClass[] { newClass };
    }
    else {
        MyClass[] withUpdates = new MyClass[local.length + 1];
        // System.arrayCopy
        withUpdates[local.length] = newClass;
        // volatile write
        myObjects = withUpdates;
    }
}

If you're going to have more than one thread updating then you're going to run into issues where you lose additions to the array as two threads could copy and old array, create a new array with their new element and then the last write would win. In that case you need to either use more synchronization or AtomicReferenceFieldUpdater

John H
  • 702
  • 3
  • 7