多线程ConcurrentModificationException

时间:2014-04-11 15:58:04

标签: java multithreading concurrentmodification

我已经在网上搜索了一段时间,试图解决这个问题,但没有成功。

在我的应用程序中,我有一大堆消息,我试图使用基本的交换加密方案进行加密。由于集合是大量的BigIntegers,我试图多线程加密以提高性能。

基本上,我接收大量消息并将其拆分为传递给加密线程的子集,以执行加密的子集。然后我尝试提取每个子集,并在线程完成所有部分之后将它们聚合到原始的大集合中。 当我遍历线程并拉出每个加密时,当我尝试将所有加密实际添加到所有加密列表中时发生错误,并且它抛出的错误是java.util.ConcurrentModificationException错误。

我试图使用同步,但它没有帮助。

这是函数调用:

protected Set<BigInteger> multiEncrypt(BigInteger key, HashSet<BigInteger> messageSet) {
    ArrayList<BigInteger> messages = new ArrayList<BigInteger>(messageSet);
    Set<BigInteger> encryptions = Collections.synchronizedSet(new HashSet<BigInteger>());
    int cores = Runtime.getRuntime().availableProcessors();
    int numMessages = messages.size();
    int stride = numMessages/cores;

    //create all the threads and run them
    ArrayList<EncryptThread> threads = new ArrayList<EncryptThread>();
    for (int thread = 0; thread < cores; thread++) {
        int start = thread*stride;
        //don't want to go over the end
        int stop = ((thread+1)*stride >= messages.size()) ? messages.size()-1 : (thread+1)*stride;
        List<BigInteger> subList = messages.subList(start, stop);
        EncryptThread t = new EncryptThread(encryptionScheme.getPrime(), key, subList);
        t.start();
        threads.add(t);
    }
    //pull out the encryptions
    synchronized(encryptions){
        for (int i=0; i < threads.size()-1; i++) {
            EncryptThread thread = threads.get(i);
            ArrayList<BigInteger> these = thread.getEncryptions();
            encryptions.addAll(these); //<-- Erroring Here
            thread.finish();
        }
    }

以下是我编写的用于执行加密的EncryptThread类的相关部分:

/**
 * Constructor
 */
public EncryptThread(BigInteger prime, BigInteger key, List<BigInteger> messages) {
    //need a new encryption scheme object for each thread
    encryptionScheme = new EncryptionScheme(prime);
    encryptions = new ArrayList<BigInteger>();
    this.key = key;
    this.messages = messages;
    wait = true;
}

@Override
public void run() {
    encryptMessages(key, messages);
    while(wait);
}

/**
 * Used to encrypt a set of messages
 * @param key
 * @param messages
 * @return
 */
public void encryptMessages(BigInteger key, List<BigInteger> messages) {
    System.out.println("Encrypting stuff");
    for (BigInteger m : messages) {
        BigInteger em = encryptionScheme.encrypt(key, m);
        encryptions.add(m);
    }
}

public ArrayList<BigInteger> getEncryptions() {
    return encryptions;
}

    //call this after encryptions have been pulled to let the thread finish
public void finish() {
    wait = false;
}

}

我不是Java的新手,但我不熟悉java中的多线程,所以我很感激任何建议。提前谢谢!

编辑:根据建议,我在EncryptThread类中添加了一个简单的锁定机制,这使得线程等待返回加密,直到它们全部完成并且现在可以正常工作。

public void encryptMessages(BigInteger key, List<BigInteger> messages) {
    System.out.println("Encrypting stuff");
    this.lock = true;
    for (BigInteger m : messages) {
        BigInteger em = encryptionScheme.encrypt(key, m);
        //deals with when we have to mark chaff at S2
        if (shift) {
            em.shiftLeft(1);
            if(shiftVal != 0) em.add(BigInteger.ONE);
        }
        encryptions.add(m);
    }
    this.lock = false;
}

public ArrayList<BigInteger> getEncryptions() {
    while(lock);
    return encryptions;
}

编辑#2 所以我最终使用的是我实验室的某个人向我建议的解决方案。我摆脱了锁和等待布尔值,以及EncryptThread类中的finish()函数,而是在start和getEncryption循环之间添加了一个简单的thread.join()循环:

    //create all the threads
    ArrayList<EncryptThread> threads = new ArrayList<EncryptThread>();
    for (int thread = 0; thread < cores; thread++) {
        int start = thread*stride;
        //don't want to go over the end
        int stop = ((thread+1)*stride >= messages.size()) ? messages.size()-1 : (thread+1)*stride;
        List<BigInteger> subList = messages.subList(start, stop);
        EncryptThread t = new EncryptThread(encryptionScheme.getPrime(), key, subList, shiftVal);
        t.start();
        threads.add(t);

    }
    //wait for them to finish
    for( EncryptThread thread: threads) {
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //pull out the encryptions
    for (int i=0; i < threads.size()-1; i++) {
        EncryptThread thread = threads.get(i);
        encryptions.addAll(thread.getEncryptions());
    }

我认为我的主要困惑是我认为线程类在完成运行后无法调用它的方法。但上述工作正常。

3 个答案:

答案 0 :(得分:0)

如果您查看List的addAll,it says the following上的文档:

将指定集合中的所有元素按指定集合的​​迭代器(可选操作)返回的顺序追加到此列表的末尾。 如果在操作正在进行时修改了指定的集合,则此操作的行为是未定义的。(请注意,如果指定的集合是此列表,并且它非空,则会发生这种情况。)

您可以在addAll正在encryptMessages方法中使用它的迭代器时看到您的列表被修改,您生成的某个线程当前正在执行。

for (BigInteger m : messages) {
    BigInteger em = encryptionScheme.encrypt(key, m);
    encryptions.add(m); // <-- here
}

我没有完全查看你的所有代码,但这里的一些东西不是线程安全的。您可以使用CopyOnWriteArrayList代替常规ArrayList来避免使用ConcurrentModificationException,如果您没有将所有内容添加到addAll中的列表中打电话,如果你不是,那么你还需要等待线程完成。您可能希望只使用ExecutorService的任务。还有其他改进可能。

另外,每个人都提到学习如何用Java编写线程安全程序的goto书是Concurrency in Practice,我建议如果你不熟悉Java中的并发性。

答案 1 :(得分:0)

当您在对其进行迭代时修改集合时会发生ConcurrentModificationException。它与多线程无关,因为您可以轻松创建单线程示例:

ArrayList<String> myStrings = new ArrayList<>();
myStrings.add("foo");
myStrings.add("bar");
for(String s : myStrings) {
   myStrings.add("Hello ConcurrentModificationException!");

答案 2 :(得分:0)

你在这里开始你的线程。

for (int thread = 0; thread < cores; thread++) {
        int start = thread*stride;
        //don't want to go over the end
        int stop = ((thread+1)*stride >= messages.size()) ? messages.size()-1 : (thread+1)*stride;
        List<BigInteger> subList = messages.subList(start, stop);
        EncryptThread t = new EncryptThread(encryptionScheme.getPrime(), key, subList);
        t.start();
        threads.add(t);
    }

好。然后你必须等待所有线程完成,然后在此块中启动聚合。

//pull out the encryptions
synchronized(encryptions){
    for (int i=0; i < threads.size()-1; i++) {
        EncryptThread thread = threads.get(i);
        ArrayList<BigInteger> these = thread.getEncryptions();
        encryptions.addAll(these); //<-- Erroring Here
        thread.finish();
    }
}

您正在阻止仅访问encryptions的线程。但是您创建的主题没有访问set。平均时间它将继续添加到自己的数组列表these。因此,当您致电encryptions.addAll(these);时,两个线程(拥有encryptions的线程和拥有these的线程

访问这些线程

其他答案提供了有关addAll中为什么出现并发异常的详细信息。

你必须等到所有线程都完成工作。

您可以使用ExecutorService

执行此操作

将您的起始线程更改为

ExecutorService es = Executors.newFixedThreadPool(cores);
for(int i=0;i<5;i++)
    es.execute(new Runnable() { /*  your task */ }); //EncryptThread instance
es.shutdown();
boolean finshed = es.awaitTermination(1, TimeUnit.MINUTES);

然后处理您的追加过程。

ExecutorService es = Executors.newFixedThreadPool(cores);
for (int thread = 0; thread < cores; thread++) {
        int start = thread*stride;
        //don't want to go over the end
        int stop = ((thread+1)*stride >= messages.size()) ? messages.size()-1 : (thread+1)*stride;
        List<BigInteger> subList = messages.subList(start, stop);
        EncryptThread t = new EncryptThread(encryptionScheme.getPrime(), key, subList);
        es.execute(t);
        threads.add(t);
    }
es.shutdown();
boolean finshed = es.awaitTermination(1, TimeUnit.MINUTES);



//pull out the encryptions
    synchronized(encryptions){
        for (int i=0; i < threads.size()-1; i++) {
            EncryptThread thread = threads.get(i);
            ArrayList<BigInteger> these = thread.getEncryptions();
            encryptions.addAll(these); //<-- Erroring Here
            thread.finish();
        }
    }

假设您的EncryptThread现在是Thread。您可能需要更改为实现Runnable。并且getEncryptions没有其他变化