刚开始学习多线程并坚持并发修改的情况。
这是我的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
答案 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方法打印到打印列表,那么至少它不会发生错误。