× Giới thiệu Lịch khai giảng Tin tức Sản phẩm học viên

Khám phá cách tránh lỗi ConcurrentModificationException trong Java

16/11/2021 13:31

Trong bài viết này, chúng ta sẽ tìm hiểu về một kiểu ngoại lệ được xác định trước trong Java là ConcurrentModificationException. Đây là một ngoại lệ phổ biến khi làm việc với Collection trong Java. Bạn cần phải nắm rõ ngoại lệ này cũng như cách tránh nó trong Java. Do vậy, chúng ta sẽ cùng tìm hiểu ngay về cách tránh lỗi ConcurrentModificationException trong Java trong bài viết này. 

ConcurrentModificationException trong Java

Bất cứ khi nào chúng ta cố gắng sửa đổi đồng thời một đối tượng mà không được phép, thì lỗi ConcurrentModificationException sẽ xảy ra. Chúng ta thường gặp phải ngoại lệ này khi chúng ta làm việc với các lớp Collection của Java. Ngoại lệ này có trong gói java.util.

ConcurrentModificationException trong Java

Các lớp Collection của Java rất nhanh hỏng và nếu chúng ta cố gắng thay đổi chúng trong khi một số luồng đang duyệt qua nó bằng cách sử dụng một trình vòng lặp, thì chúng ta sẽ gặp lỗi ConcurrentModificationException từ phương thức iterator.next ().

Ngoại lệ sửa đổi đồng thời có thể xảy ra trong môi trường lập trình Java đa luồng cũng như đơn luồng.

Hãy lấy một ví dụ. Một luồng không được phép sửa đổi Collection khi một số luồng khác đang lặp lại nó vì kết quả của việc lặp trở nên không xác định với nó. Một số triển khai của lớp Iterator ném ngoại lệ này, bao gồm tất cả các triển khai có mục đích chung của Iterator mà JRE cung cấp.

Các trình lặp như vậy thực hiện điều này được gọi là fail -fast vì chúng ném ngoại lệ ngay khi gặp tình huống như vậy thay vì phải đối mặt với hành vi không xác định của Collection bất kỳ lúc nào trong tương lai. 

Ví dụ của Java ConcurrentModificationException

package com.techvidvan.concurrentmodificationexception;

import java.awt.List;

import java.util. * ;

public class ConcurrentModificationException {

  public static void main(String[] args) {

    ArrayList < Integer > list = new ArrayList < >();




    list.add(1);

    list.add(2);

    list.add(3);

    list.add(4);

    list.add(5);




    Iterator < Integer > iterator = list.iterator();

    while (iterator.hasNext()) {

      Integer value = iterator.next();

      System.out.println("The value of the List is:" + value);

      if (value.equals(3)) list.remove(value);

    }

  }

}

Output

The value of the List is:1

The value of the List is:2

The value of the List is:3

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 ConcurrentModificationException.main(ConcurrentModificationException.java:18)

Đoạn thông báo đầu ra ở trên cho biết rằng ngoại lệ xảy ra khi chúng ta gọi phương thức tiếp theo khi trình lặp đang lặp lại danh sách và chúng ta thực hiện sửa chữ đồng thời tại đó. Nhưng nếu chúng ta thực hiện các thay đổi trong hashmap như đoạn mã dưới, thì sẽ không có ngoại lệ nào. Ví dụ:

package com.techvidvan.concurrentmodificationexception;

import java.awt.List;

import java.util. * ;

public class concurrentModificationException {

  public static void main(String[] args) {

    HashMap < Integer,

    Integer > map = new HashMap < >();

    map.put(1, 1);

    map.put(2, 2);

    map.put(3, 3);




    Iterator < Integer > it = map.keySet().iterator();

    while (it.hasNext()) {

      Integer key = it.next();

      System.out.println("The Map Value is: " + map.get(key));

      if (key.equals(2)) {

        map.put(1, 4);

      }

    }

  }

}

 

Output

The Map Value is: 1

The Map Value is: 2

The Map Value is: 3

>>> Đọc thêm: Giới thiệu Design Pattern trong Java và tìm hiểu các loại Design Pattern

 

Hàm tạo của Concurrent Modification Exception trong Java

Có 4 loại hàm tạo của ConcurrentModificationException trong Java như sau:

STT

Hàm tạo

Mô tả

1

public ConcurrentModificationException ()

Hàm tạo này tạo ra một ConcurrentModificationException mà không có tham số

2

public ConcurrentModificationException (Thông báo chuỗi)

Hàm tạo này tạo ra một ConcurrentModificationException cùng một tin nhắn chi tiết về Exception

3

public ConcurrentModificationException (Throwable Cause)

Hàm tạo này tạo một Ngoại lệ ConcurrentModification và một thông báo là (cause==null?null:cause.toString()). Sau đó chúng ta có thể lấy lại thông điệp bằng cách sử dụng Throwable.getCause(). 

4

public ConcurrentModificationException (Thông báo chuỗi,Throwable Cause)

Hàm tạo này tạo ra một ngoại lệ ConcurrentModificationException với nguyên nhân và thông tin chi tiết. Chúng ta có thể lấy lại thông tin bằng cách sử dụng Throwable.getMessage() và nguyên nhân bằng cách Throwable.getCause()

>>> Đọc thêm: Copy Constructor trong Java - Các ưu điểm và ví dụ cụ thể

Làm cách nào để tránh ConcurrentModificationException trong Java trong môi trường đa luồng?

Chúng ta có thể làm một số biện pháp phòng ngừa hoặc một số cách để tránh ConcurrentModificationException trong môi trường đa luồng như sau:

  • Chúng ta có thể lặp qua mảng thay vì lặp qua lớp tập howpk. Làm theo cách này, chúng ta có thể làm việc với các danh sách có kích thước nhỏ, nhưng điều này sẽ làm giảm hiệu suất nếu kích thước mảng lớn.
  • Khóa danh sách bằng cách đặt nó vào khối đồng bộ là một cách khác để tránh ngoại lệ sửa đổi đồng thời. Đây không phải là một cách tiếp cận hiệu quả, nó không sử dụng mục đích duy nhất của đa luồng.
  • Các lớp ConcurrentHashMap và CopyOnWriteArrayLisst của phiên bản JDK 1.5 hoặc cao hơn cũng có thể giúp chúng ta tránh các ngoại lệ sửa đổi đồng thời. 

 

Làm thế nào để tránh ngoại lệ sửa đổi đồng thời trong môi trường đơn luồng

Chúng ta có thể tránh Ngoại lệ sửa đổi đồng thời trong một môi trường luồng đơn. Chúng ta có thể sử dụng phương thức remove() của Iterator để loại bỏ đối tượng khỏi collection bên dưới. Nhưng trong trường hợp này, bạn chỉ có thể xóa cùng một đối tượng chứ không phải bất kỳ đối tượng nào khác khỏi danh sách. 

Sử dụng vòng lặp để tránh ConcurrentModificationException trong Java

Chúng ta cũng có thể sử dụng vòng lặp for thay vì một trình vòng lặp để tránh các ngoại lệ sửa đổi đồng thời trong môi trường đơn luồng. Chúng ta có thể đảm bảo rằng mã của chúng ta có các đối tượng được thêm vào trong danh sách.

Ví dụ về mã:

package com.technvidvan.concurrentmodificationexception;

import java.util.List;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.CopyOnWriteArrayList;

public class AvoidConcurrentModificationException {

  public static void main(String[] args) {

    List < String > myList = new CopyOnWriteArrayList < String > ();




    myList.add("1");

    myList.add("2");

    myList.add("3");

    myList.add("4");

    myList.add("5");




    for (int i = 0; i < myList.size(); i++) {

      System.out.println("List value: " + myList.get(i));

      if (myList.get(i).equals("3")) {

        myList.remove(i);

        i--;

        myList.add("6");

      }

    }

    System.out.println("List Size:" + myList.size());

  }

}

Output

List value: 1

List value: 2

List value: 3

List value: 4

List value: 5

List value: 6

List Size:5

 

Lỗi ngoại lệ ConcurrentModificationException sẽ xảy ra nếu bạn cố thay đổi cấu trúc của danh sách nguyên bản với danh sách phụ. Cùng xem ví dụ:

package com.technvidvan.concurrentmodificationexception;

import java.util.ArrayList;

import java.util.List;

public class ConcurrentModificationExceptionWithArrayListSubList {

  public static void main(String[] args) {




    List < String > names = new ArrayList < >();

    names.add("Java");

    names.add("PHP");

    names.add("SQL");

    names.add("Angular 2");




    List < String > first2Names = names.subList(0, 2);




    System.out.println(names + " , " + first2Names);




    names.set(1, "JavaScript");

    // check the output below. :)

    System.out.println(names + " , " + first2Names);




    // Let's modify the list size and get ConcurrentModificationException

    names.add("NodeJS");

    System.out.println(names + " , " + first2Names); // this line throws exception




  }

}

Output

[Java, PHP, SQL, Angular 2] , [Java, PHP]

[Java, JavaScript, SQL, Angular 2] , [Java, JavaScript]

Exception in thread “main” java.util.ConcurrentModificationException

at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)

at java.util.ArrayList$SubList.listIterator(ArrayList.java:1091)

at java.util.AbstractList.listIterator(AbstractList.java:299)

at java.util.ArrayList$SubList.iterator(ArrayList.java:1087)

at java.util.AbstractCollection.toString(AbstractCollection.java:454)

at java.lang.String.valueOf(String.java:2994)

at java.lang.StringBuilder.append(StringBuilder.java:131)

at com.techvidvan.concurrentmodificationexception.ConcurrentModificationExceptionWithArrayListSubList.main(ConcurrentModificationExceptionWithArrayListSubList.java:23)

 

Kết luận: Trên đây là toàn bộ thông tin về lỗi ngoại lệ ConcurrentModificationException trong Java. Chúng ta đã thảo luận về nguyên nhân dẫn đến ngoại lệ và thảo luận về những cách khác nhau để giải quyết ngoại lệ này.  Hy vọng bạn đã nắm rõ lỗi ConcurrentModificationException trong Java và cách sử dụng chúng và đừng quên tìm hiểu thêm về Java và các ngôn ngữ lập trình khác qua các khóa học lập trình tại T3H.