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