1

Like the title, given 2 arrays int[] a, int[] b shared by two Threads each of which rearrange the elements of the two arrays in a way that each element of the first array is <= of the corrispondent element of the second array a[i] <= b[i] the output seems to be always correct without the needs of synchronization

public class MyClass {

    int[] a = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
    int[] b = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    class MyThread extends Thread {
        public void run() {
                for (int i = 0; i < a.length; i++) {
                    if (a[i] > b[i]) {
                        int temp = b[i];
                        b[i] = a[i];
                        a[i] = temp;
                    }
                }
        }
    }

    public static void main(String[] args) {

        MyClass myClass = new MyClass();

        MyThread t1 = myClass.new MyThread();
        MyThread t2 = myClass.new MyThread();

        t1.start();
        t2.start();

        while (t1.isAlive() | t2.isAlive()) {
            System.out.println("waiting");
        }

        System.out.println(Arrays.toString(myClass.a));
        System.out.println(Arrays.toString(myClass.b));
    }
}

Here is the output i'm getting (on multiple runs) should i consider this only luck or is there somethig i'm missing?

a = [0, 1, 2, 3, 4, 4, 3, 2, 1, 0]
b = [9, 8, 7, 6, 5, 5, 6, 7, 8, 9]
user13121591
  • 125
  • 5
  • Both threads try to apply the _same_ changes to the arrays. If each thread executed its special operation on the array (e.g., one would add, the other would multiply or one would use `>` and the other would use `<` to arrange the elements) the results would differ from run to run. – Nowhere Man Nov 10 '21 at 20:26
  • 1
    A side note: you should use [`Thread.join`](https://docs.oracle.com/javase/tutorial/essential/concurrency/join.html) instead of a busy-loop in the main thread. Also, `|` is a bitwise operator, it has different semantics to the boolean `||` operator. – Aivean Nov 10 '21 at 23:04

3 Answers3

5

This is luck. Writes by one thread are not guaranteed to be visible to other threads unless the threads synchronize somehow. Arrays are not an exception.

There is an AtomicIntegerArray type that can help you here.

While declaring an array field as volatile doesn't extend to the elements of the array, there may be a way to establish a happens-before relationship using that. I'd have to think it through, but if it did work, it would be fairly brittle in the sense that changing the usage slightly could create a concurrency bug.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
erickson
  • 265,237
  • 58
  • 395
  • 493
  • Note: concurrent collections, while ensuring the visibility of changes won't help with the data race here. Both threads can evaluate `a[i] > b[i]` clause for the same `i` at the same time, and then try to perform swap with unpredictable results, even if every operation is atomic. Unfortunately, in this case there is no other choice, but to introduce locks for processing each individual index `i`, or synchronize the whole processing logic using a single lock. – Aivean Nov 10 '21 at 23:08
  • @Aivean I haven't re-written the code and studied it out, but I was thinking that `compareAndSet()` would be able to solve that. – erickson Nov 11 '21 at 00:23
  • Ok, I gave it some thought and you might be right. But it would be very tricky. For example, you'd have to read `a[i]` and `b[i]` twice in the following sequence: `read a, read b, read a, read b`, and then check that first read matches the second read, to ensure that other thread haven't overwritten `a` or `b` between reads. But assuming all threads write `a` and `b` in the same order, there shouldn't be a problem with write, as the second thread will always fail CAS when writing `a`. – Aivean Nov 11 '21 at 00:42
0

No, they are not. Threads will modify them how they want and there's a chance of a race condition. If you want more than 1 thread to operate on a collection at a time you need to use special thread safe collections, which have synchronisation built in, so you don't have to worry about it

The reason why your output seems correct is because there's only a small chance for this race condition to occur. You cannot protect with unit tests against it. You need to do some analytical work to prove that your code is thread safe or use a special framework for testing multithreaded stuff.

Does this answer your question ? Leave me a response in the comments

Arthur Klezovich
  • 2,595
  • 1
  • 13
  • 17
  • yes I am aware of this and precisely for this reason I was surprised by the always correct output and I thought there was something i was missing thank you for the replay – user13121591 Nov 10 '21 at 18:58
  • @user13121591 If this answer has helped you feel free to comment and upvote, so others can find and reuse it as well. – Arthur Klezovich Nov 10 '21 at 19:07
  • 2
    Just because the answer is always correct in tests doesn't mean there's not a thread-safety bug. That's part of what makes thread-safety so hard. – Louis Wasserman Nov 10 '21 at 19:15
0

No, arrays aren't threadsafe. This is luck in the sense that it works due to guarantees you weren't aware of.

In the JLS:

The final action in a thread T1 synchronizes-with any action in another thread T2 that detects that T1 has terminated.

Finding the thread died with isAlive counts for this. the synchronizes-with creates a happens-before relationship and makes sure the changes to the array are visible to the main thread. There was a data race between the two threads so it's not knowable what order their updates are be applied in but since they were doing the same thing it doesn't matter.

Sometimes code just happens to work by accident, on account of something causing a happens-before relationship, then that thing gets removed and stuff breaks mysteriously. Removing a call to System.out.println is an example, the call acquires a lock, which ends up making some change visible, then someone removes the println and is surprised that it doesn't work any more. See this question: Loop doesn't see value changed by other thread without a print statement

This kind of thing isn't something you want to rely on.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276