5
class ThreadUnsafe {

    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200; 

    public static void main(String[] args) {
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }

  
    ArrayList<String> list = new ArrayList<>();

    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {  
            method2();
            method3();
        }
    }
    private void method2() {
        list.add("1");
    }
    private void method3() {
        list.remove(0);
    }

}

The code above throws

java.lang.IndexOutOfBoundsException: Index: 0, Size: 1

I know ArrayList is not thread-safe, but in the example, I think every remove() call is guaranteed to be preceded by at least one add() call, so the code should be OK even the order is messed up like the following:

thread0: method2()
thread1: method2()
thread1: method3()
thread0: method3() 

Some explanations needed here, please.

4
  • 1
    ArrayList itself is not thread safe, so if two threads are doing add or remove calls concurrently bad things can happen. Commented Oct 9, 2020 at 10:03
  • Can you express a question please? Commented Oct 9, 2020 at 10:09
  • @Milgo Why it throws java.lang.IndexOutOfBoundsException? The code execution order can be messed up but I can not think of a case that an IndexOutOfBoundsException can happen. Commented Oct 9, 2020 at 10:17
  • @handhand The operations are not atomic, so while one is underway the state of the list is not well defined. As you yourself said, "ArrayList is not thread-safe". What do you think that means? If you want to see exactly what's happening, run your program under a debugger, with a breakpoint on IndexOutOfBoundsException. If you add the full stack trace someone might be able to tell you exactly what's happening. Commented Oct 9, 2020 at 10:34

3 Answers 3

7

If always one add() or remove() call is completely finished before another one is started, your reasoning is correct. But ArrayList doesn't guarantee that as its methods aren't synchronized. So, it can happen that two threads are in the middle of some modifying calls at the same time.

Let's look at the internals of e.g. the add() method to understand one possible failure mode.

When adding an element, ArrayList increases the size using size++. And this is not atomic.

Now imagine the list being empty, and two threads A and B adding an element at exactly the same moment, doing the size++ in parallel (maybe in different CPU cores). Let's imagine things happen in the following order:

  • A reads size as 0.
  • B reads size as 0.
  • A adds one to its value, giving 1.
  • B adds one to its value, giving 1.
  • A writes its new value back into the size field, resulting in size=1.
  • B writes its new value back into the size field, resulting in size=1.

Although we had 2 add() calls, the size is only 1. If now you try to remove 2 elements (and this time it happens sequentially), the second remove() will fail.

To achieve thread safety, no other thread should be able to mess around with the internals like size (or the elements array) while one access is currently in progress.

Multi-threading is inherently complex in that the calls from multiple threads can not only happen in any (expected or unexpected) order, but that they can also overlap, unless protected by some mechanism like synchronized. On the other hand, excessive use of the synchronization can easily lead to poor multi-thread performance, and also to dead-locks.

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

Comments

1

As a supplement to @RalfKleberhoff's answer,

I think every remove() call is guaranteed to be preceded by at least one add() call,

Yes.

so the code should be OK even the order is messed up

No, that is not a valid inference with respect to a multithreaded program.

Your program contains data races as a result of two threads both accessing the same shared, non-atomic object, with some of those accesses being writes, without appropriate synchronization. The whole behavior of a program that contains data races is undefined, so in fact you cannot draw any conclusions at all about its behavior.

Do not try to cheat or scrimp on synchronization. Do minimize the amount of it that you need by limiting your use of shared objects, but where you need it, you need it, and the rules for determining when and where you need it are not that hard to learn.

Comments

0

ArrayList in java docs says,

Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally.

Why this code is not thread safe ?

Multiple thread running on Machine runs independent of each other.

  public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {  
            method2();
            method3();
        }
    }

Here method2() and method3() are being process sequential within the thread but not across the thread. ArrayList list is common between both thread. which will be in inconstant state between both thread on multi core system.

Interesting test would be add empty check in method3() and set LOOP_NUMBER = 10000;

private void method3()
{
    if (!list.isEmpty())
        list.remove(0);
}

In result you should get same Runtime Exception some thing like java.lang.IndexOutOfBoundsException: Index: 0, Size: 1 or java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 because of same reason inconstant state of variable in list i.e. size.

To fix this issue you could have added synchronized like below or use Syncronized list

public void method1(int loopNumber)
{
    for (int i = 0; i < loopNumber; i++)
    {
        synchronized (list)
        {
            method2();
            method3();
        }
    }
} 

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.