繁体   English   中英

多线程ConcurrentModificationException

[英]Multithread ConcurrentModificationException

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

在我的应用程序中,我尝试使用基本的可交换加密方案加密大量消息。 由于集合中包含大量的BigInteger,因此我尝试对加密进行多线程处理以提高性能。

基本上,我收集大量消息并将其拆分为多个子集,这些子集传递给加密线程以进行加密的子集。 然后,我尝试提取每个子集,并在线程全部完成各自的工作之后将它们聚合为原始的大集合。 当我遍历线程并提取它们的每种加密时,当我尝试将所有加密实际添加到所有加密列表中时发生错误,并且抛出的错误是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());
    }

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

如果查看List的addAll上的文档, 它将显示以下内容

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

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

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

我没有完全浏览所有代码,但是这里的某些内容不是线程安全的。 您最好使用CopyOnWriteArrayList而不是常规的ArrayList来避免ConcurrentModificationException ,即如果可以将所有内容都没有添加到addAll调用中的列表中,就可以了,否则,您还需要等待线程完成。 您可能只想将任务与ExecutorService一起使用。 可能还有其他改进。

另外,每个人都提到要学习如何在Java中编写线程安全程序的入门书是《实践中的并发性》,如果您不熟悉Java的并发性,我建议您。

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

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

您将在此处启动线程。

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 encryptions的线程

其他答案提供了有关为何在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中没有其他更改

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM