Thread safety issue

折月煮酒 提交于 2021-01-28 02:31:47

问题


I have a LinkedList with Objects, that I want to process. Objects get added to it from another thread, but only one Thread removes/reads from it.

private LinkedList<MyObject> queue = new LinkedList<>();

new Thread()
{
    @Override
    public void run()
    {
        while (!Thread.interrupted())
        {
            if (!queue.isEmpty())
            {
                MyObject first = queue.removeFirst();
                // do sth..
            }
        }
    }
}.start();

In another Thread I add Objects to the queue

queue.add(new MyObject());

Sometimes this code leads to an Exception though, which I cant really explain to myself. Exception in thread "" java.util.NoSuchElementException at java.util.LinkedList.removeFirst(LinkedList.java:270)

I dont get, why I get this Exception, since it should only try to remove an object if one exists.


回答1:


As Nicolas has already mentioned, you need a thread safe implementation. I would recommend using LinkedBlockingQueue.

You can add to it using offer method and remove using take which will also resolve your "busy waiting" problem.




回答2:


A LinkedList is not thread-safe so you can't share it with several threads as you currently do otherwise you will face unpredictable bugs like this one due to concurrent modifications that lead to an inconsistent state, use instead a thread-safe deque such as ConcurrentLinkedDeque.




回答3:


Although I think there have been a few good solutions offered as to how to resolve the problem, none of the answers explained why @BluE sees the NoSuchElementException. So here is what I think could be happening.

Since LinkedList access is not synchronized it is possible that:

  1. The producer thread adds an element to the queue
  2. Two consumer threads concurrently check for if (!queue.isEmpty()) and see that it is not.
  3. Both consumer threads go ahead and try to take an element from the queue invoking MyObject first = queue.removeFirst();
  4. One of the threads succeeds and the other one fails with a NoSuchElementException since there are no more elements in the queue.

    UPDATE:

Provided you have only one producer and one consumer, I think the Java Memory Model specification could explain the behaviour you see.

Long story short, since the access to the LinkedList is not synchronized there are no data visibility guarantees offered by the JVM. Let's have a look at the implementations of isEmpty and removeFirst methods:

From LinkedList

transient int size = 0;
transient Node<E> first;

// ...

public int More ...size() {
    return size;
}

// ...

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

From AbstractCollection

public boolean isEmpty() {
    return size() == 0;
}

As you can see, the size and the elements are stored in different variables. So it is possible that the consumer thread sees the updates on the "size" variable and does not see the updates on the "first".




回答4:


What you could do is to use some kind of technique to coordinate the threads, like Mutex, Semaphore, Monitor, Mailbox etc.



来源:https://stackoverflow.com/questions/39976466/thread-safety-issue

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!