2016-10-11 3 views
2

У меня есть LinkedList с объектами, которые я хочу обработать. Объекты добавляются к нему из другого потока, но только один поток удаляет/читает его.Проблема безопасности нити

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(); 

В другой Thread добавить объекты в очередь

queue.add(new MyObject()); 

Иногда этот код приводит к исключению, хотя, что я не могу действительно объяснить себе. Исключение в потоке «» java.util.NoSuchElementException в java.util.LinkedList.removeFirst (LinkedList.java:270)

я не получаю, почему я получаю это исключение, так как он должен только попытаться удалить если существует.

+1

Ваш потребительский поток выполняет «ожидание», когда очередь пуста. Вероятно, вы должны исправить это. –

+0

Немного глупо, но поскольку эти вещи могут быть очень трудными для прогнозирования, я предлагаю вам убедиться, что действительно существует только один поток, вызывающий метод removeFirst. Добавьте печать идентификатора потока внутри оператора if. – TheFooBarWay

+0

@ TheFooBarWay Не имеет значения, есть ли один потребитель или два. Существует несколько производителей, которых достаточно, чтобы вызвать проблемы синхронизации. Если у вас более одного потока, изменяющего объект, он должен быть синхронизирован. Если у вас было ровно одно изменение потока и чтение нескольких потоков, тогда он может быть несинхронизирован (но повторение может быть сложным). –

ответ

2

Как уже упоминалось Николасом, вам нужна надежная реализация потока. Я бы рекомендовал использовать LinkedBlockingQueue.

Вы можете добавить его к использованию, используя метод offer и удалить с помощью take, который также разрешит вашу проблему "ожидание".

+1

Все верно, но он отвечает на фактический вопрос, который заключается в том, как мы заканчиваем исключение. Если действительно существует только один поток, удаляющий элементы, то мы можем линейный код в выражении if, и запуск должен быть законным. – TheFooBarWay

+2

@TheFooBarWay Я не думаю, что это стоит усилий, чтобы войти в подробности реализации и выяснить, что происходит в случае назначения и т. Д. Это несинхронизировано, такие проблемы могут случиться. –

+1

Не стоит усилий, может быть, если вы пришли с точки зрения программистов.Но это часто интересные академические вопросы. Я думаю, что я думаю, но мы чувствуем, что здесь мы не понимаем настоящего вопроса, предлагая решение, безопасное для потоков. Это как показать ребенку, как сделать длинное разделение, используя калькулятор. Кроме того, вы правы. – TheFooBarWay

0

LinkedList является не поточно-, так что вы не можете поделиться им с несколькими потоками в настоящее время вы в противном случае вы будете сталкиваться с непредсказуемыми ошибками, как эта из-за одновременные изменения, которые приводят к неустойчивому состоянию, используйте вместо него поточно-безопасный deque такой как ConcurrentLinkedDeque.

+0

Кстати, я не спустил вас вниз и не знаю, почему кто-то будет. Я поддержал, чтобы отменить его. На самом деле мне было трудно выбрать выбранный ответ между этим и ответом Ярослава Павлака, потому что оба были очень полезны! – BluE

-2

Что вы можете сделать, это использовать какое-то технику для координации потоков, как мьютекс, семафор, монитор, почтовый ящик и т.д.

0

Хотя я думаю, что там было несколько хороших решений, предлагаемых о том, как решить проблема, ни один из ответов не объяснил, почему @BluE видит исключение NoSuchElementException. Так вот, я думаю, что это может произойти.

Поскольку доступ LinkedList не синхронизирован, возможно, что:

  1. Производитель поток добавляет элемент в очередь
  2. Два потребительские потоки одновременно проверить, если увидеть (queue.isEmpty (!)) что это не так.
  3. Обе потоки пользователей идут вперед и пытаются взять элемент из очереди, вызывающей MyObject first = queue.removeFirst();
  4. Один из потоков успешно завершен, а другой - с отсутствием исключения NoSuchElementException, поскольку в очереди больше элементов.

    UPDATE:

При условии, у вас есть только один производитель и один потребитель, я думаю, что спецификация Java модель памяти может объяснить поведение, которое вы видите.

Короче говоря, поскольку доступ к LinkedList не синхронизирован, гарантии доступности видимости, предлагаемые JVM, отсутствуют.Давайте посмотрим на реализациях IsEmpty и removeFirst методов:

С 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); 
} 

От AbstractCollection

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

Как вы можете видеть, размер и элементы хранятся в различных переменных , Таким образом, возможно, что потребительский поток видит обновления в переменной «размер» и не видит обновления на «первом».

+1

Спасибо за вашу догадку, но это не может быть так, потому что у меня только один потребитель. – BluE

Смежные вопросы