多线程访问中的ConcurrentModificationException

时间:2017-02-02 10:29:58

标签: java multithreading collections

刚开始学习多线程并坚持并发修改的情况。

这是我的Java类

package ashish.demo.threading.basic;

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;

/**
 * Created by ashishratan on 2/2/17.
 */
public class ItemTask implements Runnable {

    private volatile boolean shutdown;
    private List<Item> itemList = new ArrayList<Item>();
    private volatile Item item;
    private volatile boolean addItemEvent;
    private volatile boolean removeItemEvent;

    @Override
    public void run() {
        while (!this.shutdown) {
            try {
                synchronized (this) {
                    if (this.item != null) {
                        this.item.setProductName("Created By:: " + Thread.currentThread().getName());
                    }
                    if (this.addItemEvent) {
                        this.itemList.add(this.item);
                        this.item=null;
                        this.addItemEvent = false;
                        this.statusDisplay();

                    }
                    if (this.removeItemEvent) {
                        this.itemList.add(this.item);
                        this.removeItemEvent = false;
                        this.statusDisplay();
                    }
                }
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Shutting down...");
    }


    public void addItem(Item item) {
        this.item = item;
        this.addItemEvent = true;
    }

    public synchronized List<Item> getItemList() {
        this.statusDisplay();
        return itemList;
    }

    public void setItemList(List<Item> itemList) {
        this.itemList = itemList;
    }

    public synchronized void shutdownHook() {
        this.statusDisplay();
        this.shutdown = true;
        System.out.println(this.getItemList());
    }

    private synchronized void statusDisplay() {

        System.out.println(Thread.currentThread());
        System.out.println("Total Items In Stock are " + this.itemList.size());
    }
}

亚军类

    package ashish.demo.threading;

    import ashish.demo.threading.basic.Item;
    import ashish.demo.threading.basic.ItemTask;

    import java.util.Scanner;

    public class Main {

        public static void main(String[] args) {

            ItemTask itemTask = new ItemTask();
            Thread thread =null;

            for (int i = 0; i < 500; i++) {
                thread=new Thread(itemTask);
                thread.setName("ItemTask-Thread-"+(i+1));
                thread.setPriority(Thread.MAX_PRIORITY);
                thread.start();
            }
            System.out.println("Please Enter Number (0) to exit");
            Scanner scanner = new Scanner(System.in);
            int i = scanner.nextInt();
            while (i>0){
                itemTask.addItem(new Item(1,12.0f,"Product "+i,(byte)12));
                System.out.println(itemTask.getItemList()); // Line #26, Exception
                System.out.println("Please Enter Number (0) to exit");
                i = scanner.nextInt();
            }
            System.out.println("EXIT");
            itemTask.shutdownHook();
        }
    }


    package ashish.demo.threading.basic;

    import java.io.Serializable;

    /**
     * Created by ashishratan on 2/2/17.
     */
    public class Item implements Serializable {

        private Integer orderId;
        private Float price;
        private String productName;
        private byte category;

        public Item(Integer orderId, Float price, String productName, byte category) {
            this.orderId = orderId;
            this.price = price;
            this.productName = productName;
            this.category = category;
        }

        public Item() {
        }

        public Integer getOrderId() {
            return orderId;
        }

        public void setOrderId(Integer orderId) {
            this.orderId = orderId;
        }

        public Float getPrice() {
            return price;
        }

        public void setPrice(Float price) {
            this.price = price;
        }

        public String getProductName() {
            return productName;
        }

        public void setProductName(String productName) {
            this.productName = productName;
        }

        public byte getCategory() {
            return category;
        }

        public void setCategory(byte category) {
            this.category = category;
        }

        @Override
        public String toString() {
            return "Item{" +
                    "orderId=" + orderId +
                    ", price=" + price +
                    ", productName='" + productName + '\'' +
                    ", category=" + category +
                    '}';
        }


        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Item)) return false;

            Item item = (Item) o;

            if (getCategory() != item.getCategory()) return false;
            if (getOrderId() != null ? !getOrderId().equals(item.getOrderId()) : item.getOrderId() != null) return false;
            if (getPrice() != null ? !getPrice().equals(item.getPrice()) : item.getPrice() != null) return false;
            return getProductName() != null ? getProductName().equals(item.getProductName()) : item.getProductName() == null;
        }

        @Override
        public int hashCode() {
            int result = getOrderId() != null ? getOrderId().hashCode() : 0;
            result = 31 * result + (getPrice() != null ? getPrice().hashCode() : 0);
            result = 31 * result + (getProductName() != null ? getProductName().hashCode() : 0);
            result = 31 * result + (int) getCategory();
            return result;
        }
    }

异常追踪

    Please Enter Number (0) to exit
    3
    Thread[main,5,main]
    Total Items In Stock are 0
    []
    Please Enter Number (0) to exit
    Thread[ItemTask-Thread-455,10,main]
    Total Items In Stock are 1
    6
    Thread[main,5,main]
    Total Items In Stock are 1
    Thread[ItemTask-Thread-464,10,main]
    Total Items In Stock are 2
    Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
        at java.util.ArrayList$Itr.next(ArrayList.java:851)
        at java.util.AbstractCollection.toString(AbstractCollection.java:461)
        at java.lang.String.valueOf(String.java:2994)
        at java.io.PrintStream.println(PrintStream.java:821)
        at ashish.demo.threading.Main.main(Main.java:26)
    12

3 个答案:

答案 0 :(得分:4)

Java Concurrency In Practice 中的建议是:“谨防隐式迭代”。你在线上有隐式迭代:

System.out.println(itemTask.getItemList());

因为需要迭代此列表才能将其转换为字符串。

itemTask.getItemList()被同步的事实是无关紧要的 - 该监视器仅在调用itemTask.getItemList()期间保持:一旦返回结果,监视器就不再被保留,这意味着将结果传递给System.out.println时,监视器未被保留。

为了确保您在打印时能够独占访问项目列表,请在itemTask上明确同步:

synchronized (itemTask) {
  System.out.println(itemTask.getItemList());
}

这将在System.out.println通话期间正确保持显示器; itemTask中的修改不能同时发生,因为这些修改发生在synchronized (this)块中,其中“this”是您在外部同步的itemTask

另一种方法是从getItemList方法返回列表的防御性副本:

public synchronized List<Item> getItemList() {
    this.statusDisplay();
    return new ArrayList<>(itemList);
}

这样就不需要对getItemList的调用进行外部同步,这是一种更安全的设计,因为你不依赖于你班级的客户来“做正确的事”。

答案 1 :(得分:0)

您正在打印项目列表,其中涉及使用Iterator迭代列表,而在ItemTask线程中发生了结构修改(添加项目)。迭代器认为这很糟糕,因此以一个例外来阻止世界。这称为失败快速行为。您将不得不使用同步列表(这是错误的)或使锁定/互斥锁,每个想要修改或读取的线程都应该获取。

顺便说一句,那里发生了一些极端的争论,因为你使用了多达500个线程来更新一个对象的状态。

答案 2 :(得分:0)

如果输入缓慢打印,程序不会始终如一地抛出并发修改异常。但如果输入速度非常快,它就会经常抛出它。

根据我的理解,根本原因: 虽然ItemTask中的getItemList方法是同步的,但它只是将集合返回给Main类。集合的迭代是在Main方法中完成的,该方法是不同步的,并且如果其他线程将任何元素放入集合中,迭代器将验证是否对集合进行了任何修改,如果有任何修改则抛出异常[从堆栈跟踪中显而易见]的java.util.ArrayList $ Itr.checkForComodification(ArrayList.java:901)。

如果你在getItemList方法中将itemList的打印移动到Itemtask或者在Itemtask中将新的synchronize方法打印到打印列表,那么至少它不会发生错误。