Я новичок в многопоточном программировании и задаюсь вопросом. Как получить каждый поток для итерации по всем элементам в списке, добавляемом другим потоком?Итерация через список, изменяемый другим потоком
Вот простую программу для демонстрации. У меня есть один список целых чисел и 10 потоков, пронумерованных от 1 до 10, которые работают над ним. Каждый поток должен записывать все значения в списке в StringBuilder. После того, как поток записывает все значения в списке, он добавляет его номер в список, а затем завершает работу.
Я пытаюсь, чтобы каждый поток продолжал проверять список элементов до тех пор, пока список больше не будет изменен никаким другим потоком, но у меня возникнут проблемы с блокировкой на нем. В случае успеха, эта программа будет иметь выход, который может выглядеть как:
3: 1,
8: 1,3,2,4,5,7,
6: 1,3,2,4,5,7,8,
9: 1,3,2,4,5,7,8,6,
7: 1,3,2,4,5,
10: 1,3,2,4,5,7,8,6,9,
5: 1,3,2,4,
4: 1,3,2,
2: 1,3,
1:
Что случается иногда, но часто два или более нитей закончить до того, как замок установлен, поэтому итерация заканчивается преждевременно:
1:
2: 1,5,4,8,7,3,10,
10: 1,5,4,8,7,3,
9: 1,5,4,8,7,3,10,2,
3: 1,5,4,8,7,
7: 1,5,4,8,
5: 1, <<one of these threads didn't wait to stop iterating.
4: 1, <<
8: 1,5,4,
6: 1,5,4,8,7,3,10,2,
У кого-нибудь есть идеи?
===========
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class ListChecker implements Runnable{
static List<Integer> list = new ArrayList<Integer>();
static ReentrantLock lock = new ReentrantLock();
int id;
StringBuilder result = new StringBuilder();
public ListChecker(int id){
this.id = id;
}
@Override
public void run() {
int i=0;
do{
while (i < list.size()){
result.append(list.get(i++)).append(',');
}
if (!lock.isLocked()){
break;
}
}while (true);
addElement(id);
System.out.println(id + ": " + result.toString());
}
public void addElement(int element){
try{
lock.lock();
list.add(element);
}finally{
lock.unlock();
}
}
public static void main(String[] args){
for(int i=1; i<=10; i++){
ListChecker checker = new ListChecker(i);
new Thread(checker).start();
}
}
}
Edit: Спасибо за помощь до сих пор. Я должен уточнить, что я хотел бы, чтобы каждый поток повторялся через список в одно и то же время. В моем случае есть много обработки, которая должна выполняться по каждому элементу списка по каждому потоку (вместо добавления к StringBuffer, я делаю много сравнений элемента-кандидата со списком финалистов). Таким образом, для того, чтобы каждый поток мог работать над одним и тем же списком одновременно, для многопоточности требуется улучшить мою производительность. Таким образом, я не думаю, что блокировка вокруг всей итерации, или включение всей итерации - это синхронизированный (список) блок, будет работать.
Редактировать 2: Я думаю, что получил. Хитрость заключалась не только в синхронизации в списке при добавлении в него элементов, но и при определении того, есть ли еще элементы. Это препятствует тому, чтобы поток 2 прекратил свою итерацию до того, как поток 1 завершит добавление в список. Он выглядит немного глупым, но это сохраняет код, который мне нужно запускать в нескольких потоках вне блока синхронизации, поэтому мой реальный случай должен получить увеличение производительности, в котором я нуждаюсь.
Спасибо всем, кто помог!
import java.util.ArrayList;
import java.util.List;
public class ListChecker2 implements Runnable{
static List<Integer> list = new ArrayList<Integer>();
int id;
StringBuilder result = new StringBuilder();
public ListChecker2(int id){
this.id = id;
}
@Override
public void run() {
int i = 0;
do{
synchronized (list) {
if (i >= list.size()){
list.add(id);
System.out.println(id + ": " + result.toString());
return;
}
}
result.append(list.get(i++)).append(',');
System.out.println("running " + id);
}while(true);
}
public static void main(String[] args){
for(int i=1; i<=30; i++){
ListChecker2 checker = new ListChecker2(i);
new Thread(checker).start();
}
}
}
Опасность состоит в том, что один поток попадет в конец списка (1) и добавит новую запись (2), в то время как другой поток добавит свою запись между (1) и (2). После этого у вас будет поток, который не просмотрел все записи. Не могли бы вы использовать специально созданный список, который автоматически блокирует себя в вашем потоке, как только он будет итерации к последней записи? – OldCurmudgeon