The problem
The problem looks like this: When the order is created, shipping is selected based on the priority of the configured shipping policy. Express priority For example: SF Express, priority 1; Zto Express, Priority 2; Yto Express, Priority 3; Huitong Express, priority 4. (A smaller value indicates a higher priority.) I prioritized the entire delivery policy in the cache (Guava cache). The collections. sort method is used to implement the comparator method and sort the packages in ascending order (i.e. priority is a global variable). In a multi-threaded concurrent cases (multiple orders at the same time express), a Java. Util. ConcurrentModificationException
repetition
Let’s reproduce the problem with a Demo.
Priority data structure definition
public class PriorityDto {
private Long id;
private Long createUserId;
private Date createTime;
private List<PriorityDetailDto> detailDtos;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCreateUserId() {
return createUserId;
}
public void setCreateUserId(Long createUserId) {
this.createUserId = createUserId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public List<PriorityDetailDto> getDetailDtos() {
return detailDtos;
}
public void setDetailDtos(List<PriorityDetailDto> detailDtos) { this.detailDtos = detailDtos; }}Copy the code
public class PriorityDetailDto {
private Long detailId;
private Long id;
private Integer priority;
public Long getDetailId() {
return detailId;
}
public void setDetailId(Long detailId) {
this.detailId = detailId;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
@Override
public String toString() {
return "PriorityDetailDto{" +
"detailId=" + detailId +
", id=" + id +
", priority=" + priority +
'} '; }}Copy the code
The test code
Public class Demo {public static void main(String [] args) throws Exception {// Express priority is a global variable for all threads PriorityDto DTO = init(); Runnable runnable = newRunnable() {
@Override
public void run(){// Sort collections.sort (to.getdetaildtos (), new Comparator<PriorityDetailDto>() { @Override public int compare(PriorityDetailDto o1, PriorityDetailDto o2) {returno1.getPriority().compareTo(o2.getPriority()); }}); System.out.println(dto.getDetailDtos().toString()); }}; ExecutorService executorService = Executors.newFixedThreadPool(10); // Use 1000 thread emulationfor(int i=0; i<1000; i++) { executorService.execute(runnable); } // Initialize data private static PriorityDtoinit() {
PriorityDto dto = new PriorityDto();
dto.setId(1L);
dto.setCreateTime(new Date());
dto.setCreateUserId(-1L);
List<PriorityDetailDto> detailDtos = new ArrayList<>();
PriorityDetailDto detailDto = new PriorityDetailDto();
detailDto.setDetailId(1L);
detailDto.setId(1L);
detailDto.setPriority(2);
detailDtos.add(detailDto);
PriorityDetailDto detailDto1 = new PriorityDetailDto();
detailDto1.setDetailId(2L);
detailDto1.setId(1L);
detailDto1.setPriority(3);
detailDtos.add(detailDto1);
PriorityDetailDto detailDto2 = new PriorityDetailDto();
detailDto2.setDetailId(3L);
detailDto2.setId(1L);
detailDto2.setPriority(1);
detailDtos.add(detailDto2);
dto.setDetailDtos(detailDtos);
returndto; }}Copy the code
The results
why
The sort method of ArrayList is used above. In the Collections in Java
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
Copy the code
In the ArrayList. In Java
@Override
@SuppressWarnings("unchecked") public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; //1 Arrays.sort((E[]) elementData, 0, size, c); / / 2if(modCount ! = expectedModCount) { //3 throw new ConcurrentModificationException(); } modCount++; / / 4}Copy the code
Note modCount in the entry method. 2. Sort array elements elementData according to the rules of comparator C. 3. Determine whether concurrent changes have been made. If so, throw exceptions. ModCount increments by 1. ModCount is a variable in AbstractList. Protected TRANSIENT int modCount = 0; If multiple threads concurrently modify modCount, modCount! = expectedModCount
The solution
1. Space for time: the sorted collection for each thread is privatized, the data remains unchanged, but the sorted collection access area is only inside the thread. Such as:
Runnable runnable = new Runnable() {
@Override
public void run(){
List<PriorityDetailDto> detailDtos = new ArrayList<>(dto.getDetailDtos());
Collections.sort(detailDtos, new Comparator<PriorityDetailDto>() {
@Override
public int compare(PriorityDetailDto o1, PriorityDetailDto o2) {
returno1.getPriority().compareTo(o2.getPriority()); }}); System.out.println(detailDtos.toString()); }};Copy the code
2. You can also use lock or synchronized to lock the sorted parts, or use CopyOnWriteArrayList instead of ArrayList and Vector. From a performance point of view, the first is better.
conclusion
How to avoid ConcurrentModificationException traverse a collection: has said the API documentation! You can only use iterators to delete during iteration!
Single threaded case
(1) Use the remove method provided by Iterator to remove the current element.
(2) Establish a set to record the elements to be deleted, and then delete them uniformly.
(3) Do not use Iterator to iterate, you need to ensure that the index is normal.
(4) using the concurrent collection class to avoid ConcurrentModificationException, such as using CopyOnArrayList, rather than the ArrayList.
Multithreading case
Use concurrent collection classes, such as ConcurrentHashMap or CopyOnWriteArrayList.
Follow the public account, read more wonderful articles