在Java中使用多个线程尝试对Arraylist的数字求和时发生ConcurrentModificationException

时间:2019-06-28 20:02:13

标签: java multithreading synchronized

一般来说,我是多线程的新手,所以我仍然不太了解它,所以我不明白为什么我的以下代码有问题。我正在尝试使用前1000个数字填充ArrayList,然后使用三个线程将所有数字相加。

public class Tst extends Thread {
    private static int sum = 0;
    private final int MOD = 3;
    private final int compare;
    private static final int LIMIT = 1000;
    private static ArrayList<Integer> list = new ArrayList<Integer>();

    public Tst(int compare){
        this.compare=compare;
    }

    public synchronized void populate() throws InterruptedException{
        for(int i=0; i<=Tst.LIMIT; i++){
            if (i%this.MOD == this.compare){
            list.add(i);
            }
        }
    }

    public synchronized void sum() throws InterruptedException{
        for (Integer ger : list){
            if (ger%MOD == this.compare){
                sum+=ger;
            }
        }
    }

    @Override
    public void run(){
        try {
            populate();
            sum();
            System.out.println(sum);
        } catch (InterruptedException ex) {
            Logger.getLogger(Tst.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static void main(String[] args) {
        Tst tst1 = new Tst(0);
        tst1.start();
        Tst tst2 = new Tst(1);
        tst2.start();
        Tst tst3 = new Tst(2);
        tst3.start();
    }
}

理论上它应该显示“500.500‬”,但我得到的是

162241
328741
Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
    at tst.Tst.sum(Tst.java:38)
    at tst.Tst.run(Tst.java:50)
BUILD SUCCESSFUL (total time: 2 seconds)

4 个答案:

答案 0 :(得分:2)

之所以出现此问题,是因为您的方法是在“对象级别”中同步的,我的意思是,它使用的监视器锁是特定对象(tst1,tst2,tst3)的。换句话说,每个同步方法都使用不同的锁。 第一步,将同步方法更改为静态。

答案 1 :(得分:0)

当tst1的运行在for-each中计算总和时,则tst2的运行可能会增加列表的大小。因此,它引发并发修改异常。使用联接会有所帮助。

public static void main(String[] args) {
    Tst tst1 = new Tst(0);
    tst1.start();
    tst1.join()
    Tst tst2 = new Tst(1);
    tst2.start();
    tst1.join()
    Tst tst3 = new Tst(2);
    tst3.start();
}

答案 2 :(得分:0)

您误解了resource "google_container_cluster" "cluster" { # ... } provider "helm" { kubernetes { host = "https://${google_container_cluster.cluster.endpoint}" username = "${google_container_cluster.cluster.master_auth.0.username}" password = "${google_container_cluster.cluster.master_auth.0.password}" client_certificate = "${google_container_cluster.cluster.master_auth.0.client_certificate}" client_key = "${google_container_cluster.cluster.master_auth.0.client_key}" cluster_ca_certificate = "${google_container_cluster.cluster.master_auth.0.cluster_ca_certificate}" } } 方法的语义,您的情况下每个方法都使用不同的锁对象,请按以下方式进行操作:

synchronized

答案 3 :(得分:0)

您使用同步方法并没有实现您认为正在做的事情。编写代码的方式,保护“ sum”和“ populate”方法 从同一时间运行,但仅在同一线程实例上运行。这意味着对单个Tst对象的“ sum”和“ populate”调用一次会发生一次, 但是允许在不同对象实例上同时调用“ sum”。

在方法上使用synchronized等效于编写包装的方法 在整个方法主体中使用synchronized(this) { ... }。通过创建三个不同的实例– tst1tst2tst3 –这种同步形式 不能保护跨对象实例。相反,它可以保证在单个对象上一次只能运行populatesum中的一个;任何其他呼叫之一 这些方法(在同一对象实例上)将等到前一个方法完成。 看看Java语言规范中的8.4.3.6. synchronized Methods 有关更多详细信息。

您对static的使用可能也没有按照您的想法做。您的代码还在Tst线程类的所有实例(即sumlist)之间共享内容。由于这些定义为static, 将有一个sum和一个list。您的代码中没有线程安全性,以防止同时更改其中任何一个。 例如,随着线程的更新 “ sum”(行:sum+=ger),结果将是不确定的。也就是说,您每次运行它都可能会看到不同的结果。

具有多个线程和单个静态变量的意外行为的另一个示例是list –随着时间的推移,它会不断增长,这可能导致并发问题。 The Javadoc说:

请注意,此实现未同步。如果有多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。

修改包括添加值以及增加后备阵列存储。如果未指定起始大小– new ArrayList() –根据您使用的JVM版本,它将默认为10或其他一些较小的数字。一旦一个线程尝试添加超过ArrayList容量的项目,它将触发自动调整大小。

每个ArrayList实例都有一个容量。容量是用于在列表中存储元素的数组的大小。它总是至少与列表大小一样大。将元素添加到ArrayList后,其容量会自动增长。除了添加元素具有固定的摊销时间成本这一事实外,没有指定增长策略的详细信息。