【问题标题】:ConcurrentModificationException when trying to sum numbers of an Arraylist using multiple threads in Java尝试在 Java 中使用多个线程对 Arraylist 的数量求和时出现 ConcurrentModificationException
【发布时间】:2019-06-28 20:02:13
【问题描述】:

总的来说,我是多线程的新手,所以我仍然不完全理解它。我不明白为什么我的代码有问题。我正在尝试使用前 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)

【问题讨论】:

  • 不相关:请在您的代码中使用有意义的(和可发音的)名称。 Tst ...没有任何意义。为什么不称它为 ListAccessThreadTester ... 或类似的名称。
  • 您没有采取任何措施来阻止一个线程进入sum() 函数,而其他线程可能仍在populate() 调用中。如果在for(Integer ger : list) 循环运行时对列表进行了任何 更改,则会引发异常。不同的线程对列表的不同成员进行操作并不重要。
  • 你的同步没用,因为每个线程都在自己同步。尝试将您的方法设为静态,您会发现不同。

标签: java multithreading synchronized


【解决方案1】:

问题正在发生,因为您的方法在“对象级别”同步,我的意思是,它使用的监视器锁属于特定对象(tst1、tst2、tst3)。换句话说,每个同步方法都使用不同的锁。 作为修复它的第一步,将同步方法更改为静态。

【讨论】:

  • 谢谢,它解决了他们的问题。但是,如果我这样做,我将无法使用“比较”变量(我需要每个线程具有不同的值,因此不会重复将数字添加到列表或求和)。
  • 有多种方法可以使用“比较”变量解决最后一个问题。最简单的方法是将实例变量作为参数传递给新的静态方法(求和和填充)
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    你误解了synchronized方法的语义,在你的情况下每个使用不同的锁对象,这样做:

     class SynchList {
       private int sum = 0;
       private final int MOD = 3;
       private int compare;
       private final int LIMIT = 1000;
       private ArrayList<Integer> list = new ArrayList<Integer>();
    
       public synchronized void populate( int compare) throws InterruptedException{
           for(int i=0; i<=LIMIT; i++){
               if (i%this.MOD == compare){
               list.add(i);
               }
           }
       }
    
       public synchronized void sum( int compare ) throws InterruptedException{
           for (Integer ger : list){
               if (ger%MOD == compare){
                   sum+=ger;
               }
               System.out.println( sum );
           }
       }
    }
    
    class Tst extends Thread {
        int compare;
        SynchList synchList;
        public Tst(int compare, SynchList synchList)
        {
            this.compare= compare;
            this.synchList = synchList;
        }
        @Override
        public void run(){
            try {
                synchList.populate( compare );
                synchList.sum( compare );
    
            } catch (InterruptedException ex) {
                Logger.getLogger(Tst.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    
    public class Main
    {
        public static void main(String[] args) {
            SynchList synchList = new SynchList();
    
            Tst tst1 = new Tst( 0 , synchList );
            tst1.start();
            Tst tst2 = new Tst( 1, synchList );
            tst2.start();
            Tst tst3 = new Tst( 2, synchList );
            tst3.start();
        }
    }
    

    【讨论】:

      【解决方案4】:

      您对同步方法的使用并没有像您认为的那样做。您的代码编写方式,方法“sum”和“populate”受到保护 从同时运行,但仅在同一个线程实例上。这意味着对单个 Tst 对象的“求和”和“填充”调用将一次发生一个, 但同时对不同对象实例的“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()——它将默认为 10 或可能是其他一些相对较小的数字,具体取决于您使用的 JVM 版本。一旦一个线程尝试添加超过 ArrayList 容量的项目,它将触发自动调整大小。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-05-12
        • 1970-01-01
        • 2020-11-05
        • 2016-06-20
        • 2018-05-06
        • 1970-01-01
        相关资源
        最近更新 更多