I’m new to multithreaded programming, and have a question. How do I get each thread to iterate through all the elements in a list being added to by a different thread?
Here’s a simple program to demonstrate. I have a single list of integers, and 10 threads, numbered 1 through 10, that are working on it. Each thread is to write all the values in the list to a StringBuilder. After a thread writes all the values in the list, it adds its number to the list, then terminates.
I’m trying to get each thread to keep checking the list for elements until the list is no longer being modified by any other thread, but am having trouble with the locking on it. If successful, the program would have output that might look like:
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:
Which happens sometimes, but often two or more of the threads finish before the lock is set, so the iteration ends prematurely:
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,
Does anyone have any ideas?
===========
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: Thanks for the help so far. I should clarify, that I would like for each thread to be iterating through the list at the same time. In my case, there’s a lot of processing that needs to be performed on each element of the list by each thread (instead of appending to a StringBuffer, I’m doing a lot of comparisons of a candidate item to a list of finalists). So, having each thread be able to work on the same list at the same time is required for the multithreading to improve my performance. So, I don’t think locking around the entire iteration, or putting the whole iteration is a synchronized(list) block, will work.
Edit 2: I think I got it. The trick was not just to synchronize on the list when adding elements to it, but also when determining if there are any more elements. This prevents thread 2 from stopping its iteration before thread 1 finishes adding to the list. It looks a bit kludgy, but this keeps the code that I need to run in multiple threads outside the synchronize block, so my real case should get the performance increase I need.
Thanks to everyone who helped!
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();
}
}
}
You can do this much more simply by synchronizing on the list.
This is my output. A bit boring I’m afraid but it proves it works.
Added
If you need to avoid locking the whole list (as your edit suggests) you could try a special list that locks itself whenever it delivers the last entry. You then need to specifically unlock it of course.
Sadly, this technique does not handle the empty list situation very well. Perhaps you can think of an apt solution to that.
Output (notice the mishandling of an empty list):