迭代通过另一个线程修改的列表

时间:2012-03-28 20:50:32

标签: java multithreading

我是多线程编程的新手,并且有一个问题。如何让每个线程迭代通过不同线程添加的列表中的所有元素?

这是一个简单的演示程序。我有一个整数列表,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();
    }   
}   
}

编辑:感谢您的帮助到目前为止。我应该澄清,我希望每个线程同时迭代列表。在我的情况下,每个线程需要对列表的每个元素执行大量处理(而不是附加到StringBuffer,我正在对候选项目与决赛入围者列表进行大量比较)。因此,多线程需要让每个线程能够同时在同一个列表上工作,以提高我的性能。所以,我不认为锁定整个迭代,或者整个迭代是一个同步(列表)块,将起作用。


编辑2:我想我明白了。诀窍不仅在于向列表添加元素时在列表上进行同步,而且在确定是否还有其他元素时也是如此。这可以防止线程2在线程1完成添加到列表之前停止其迭代。它看起来有点kludgy,但这保留了我需要在同步块之外的多个线程中运行的代码,所以我的真实情况应该得到我需要的性能提升。

感谢所有帮助过的人!

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

4 个答案:

答案 0 :(得分:1)

你选择了一个难以让你的脚湿透的情况。同时读取和写入对象很复杂,从不推荐并经常被特别阻止(即不能使用迭代器和列表)

BUT

如果必须这样做,则应在添加元素之前在列表上进行同步,并在添加元素后调用list.notifyAll,这将唤醒等待更多结果的线程。同时,你的其他线程应该读到最后,然后在列表上同步(你需要在对象上同步来调用wait,notify或notifyAll)并调用wait。

BTW更简单的方法是使用listener / observer / observable模式,尽管listener patternss操作(通常)单线程。您可能需要谷歌搜索相关教程。

答案 1 :(得分:1)

您可以通过在列表上同步来更简单地完成此任务。

public class ListChecker implements Runnable {
  // Number of threads.
  static final int THREADS = 10;
  // The list.
  static final List<Integer> list = new ArrayList<Integer>(THREADS);

  // My ID
  int id;

  public ListChecker(int id) {
    // Remember my ID.
    this.id = id;
  }

  @Override
  public void run() {
    // My string.
    StringBuilder result = new StringBuilder ();
    // Wait for exclusive access to the list.
    synchronized (list) {
      // Build my string.
      for ( Integer i : list ) {
        result.append(i).append(",");
      }
      // Add me to the list.
      list.add(id);
    }
    System.out.println(id + ": " + result.toString());
  }

  public static void main(String[] args) {
    for (int i = 1; i <= THREADS; i++) {
      ListChecker checker = new ListChecker(i);
      new Thread(checker).start();
    }
  }
}

这是我的输出。我很害怕,但它证明它有效。

1: 
2: 1,
3: 1,2,
4: 1,2,3,
5: 1,2,3,4,
6: 1,2,3,4,5,
7: 1,2,3,4,5,6,
8: 1,2,3,4,5,6,7,
9: 1,2,3,4,5,6,7,8,
10: 1,2,3,4,5,6,7,8,9,

<强>加

如果您需要避免锁定整个列表(如编辑所示),您可以尝试一个特殊的列表,只要它传递最后一个条目就会自行锁定。然后你需要专门解锁它。

遗憾的是,这种技术不能很好地处理空列表情况。也许你可以想到一个合适的解决方案。

public class ListChecker implements Runnable {
  // Number of threads.
  static final int THREADS = 20;
  // The list.
  static final EndLockedList<Integer> list = new EndLockedList<Integer>();
  // My ID
  int id;

  public ListChecker(int id) {
    // Remember my ID.
    this.id = id;
  }

  @Override
  public void run() {
    // My string.
    StringBuilder result = new StringBuilder();
    // Build my string.
    for (int i = 0; i < list.size(); i++) {
      result.append(i).append(",");
    }
    // Add me to the list.
    list.add(id);
    // Unlock the end lock.
    list.unlock();
    System.out.println(id + ": " + result.toString());
  }

  public static void main(String[] args) {
    for (int i = 0; i < THREADS; i++) {
      ListChecker checker = new ListChecker(i + 1);
      new Thread(checker).start();
    }
  }

  private static class EndLockedList<T> extends ArrayList<T> {
    // My lock.
    private final Lock lock = new ReentrantLock();
    // Were we locked?
    private volatile boolean locked = false;

    @Override
    public boolean add(T it) {
      lock.lock();
      try {
        return super.add(it);
      } finally {
        lock.unlock();
      }
    }

    // Special get that locks the list when the last entry is got.
    @Override
    public T get(int i) {
      // Get it.
      T it = super.get(i);
      // If we are at the end.
      if (i == size() - 1) {
        // Speculative lock.
        lock.lock();
        // Still at the end?
        if (i < size() - 1) {
          // Release the lock!
          lock.unlock();
        } else {
          // Remember we were locked.
          locked = true;
          // It is now locked for putting until specifically unlocked.
        }
      }
      // That's the one.
      return it;
    }

    // Unlock.
    void unlock() {
      if (locked) {
        locked = false;
        lock.unlock();
      }
    }
  }
}

输出(注意空列表的错误处理):

2: 
8: 
5: 
1: 
4: 
7: 
6: 
9: 
3: 
10: 0,1,2,3,4,5,6,7,8,
11: 0,1,2,3,4,5,6,7,8,9,
12: 0,1,2,3,4,5,6,7,8,9,10,
13: 0,1,2,3,4,5,6,7,8,9,10,11,
14: 0,1,2,3,4,5,6,7,8,9,10,11,12,
15: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,
16: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,
17: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
18: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
19: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,
20: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,

答案 2 :(得分:0)

您可以使用Collections.synchronizedList(列表列表)

之类的方法

这就是你如何使用它:

   List list = Collections.synchronizedList(new ArrayList());
   ...
   synchronized (list) {
   Iterator i = list.iterator(); // Must be in synchronized block
   while (i.hasNext())
       foo(i.next());
   }

答案 3 :(得分:0)

请更新您的运行方法。并告诉我它是否按照你的期望工作?

public void run() {
int i=0;
    lock.lock();
    while (i < list.size()){            
      result.append(list.get(i++)).append(',');
}   
addElement(id);
lock.unlock();``
System.out.println(id + ": " + result.toString());
}