Потоки вашего производителя и потребителя используют одну и ту же блокировку. Когда потребитель просыпается и потребляет элемент, он вызывает lock.notifyAll()
, что разбудит всех потребителей и производителей, ожидающих его. Другой потребитель просыпается, думая, что в списке есть элементы, но он удаляет первый элемент из пустого списка, вызывая исключение.
В потребителе вместо if(list.size()==0)
используйте while(list.size()==0)
. Аналогичные рассуждения применимы и к производителю. То, что поток проснулся, не означает, что условие, в котором он находится, является верным. Это только означает, что до того, как нить проснулась, условие было истинным. Он должен проверить это снова.