2016-08-09 20 views
6

Tôi đã có một ArrayList đang được khởi tạo và được điền vào chủ đề nền (tôi sử dụng nó để lưu trữ dữ liệu Cursor). Đồng thời nó có thể được truy cập trên chủ đề chính và được lặp lại thông qua việc sử dụng foreach. Vì vậy, điều này rõ ràng có thể dẫn đến ném một ngoại lệ.An toàn chủ đề khi lặp qua một ArrayList bằng cách sử dụng foreach

Câu hỏi của tôi là cách tốt nhất để làm cho trường lớp này an toàn mà không cần sao chép mỗi lần hoặc sử dụng cờ?

class SomeClass { 

    private final Context mContext; 
    private List<String> mList = null; 

    SomeClass(Context context) { 
     mContext = context; 
    } 

    public void populateList() { 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       mList = new ArrayList<>(); 

       Cursor cursor = mContext.getContentResolver().query(
         DataProvider.CONTENT_URI, null, null, null, null); 
       try { 
        while (cursor.moveToNext()) { 
         mList.add(cursor.getString(cursor.getColumnIndex(DataProvider.NAME))); 
        } 
       } catch (Exception e) { 
        Log.e("Error", e.getMessage(), e); 
       } finally { 
        if (cursor != null) { 
         cursor.close(); 
        } 
       } 
      } 
     }).start(); 
    } 

    public boolean searchList(String query) { // Invoked on the main thread 
     if (mList != null) { 
      for (String name : mList) { 
       if (name.equals(query) { 
        return true; 
       } 
      } 
     } 

     return false; 
    } 
} 

Trả lời

3

Nói chung nó là một ý tưởng rất xấu đến hoạt động đồng thời trên một datastructure không được thread-safe . Bạn không đảm bảo rằng việc triển khai sẽ không thay đổi trong tương lai, điều này có thể ảnh hưởng nghiêm trọng đến hành vi thời gian chạy của ứng dụng, tức là java.util.HashMap gây ra các vòng lặp vô hạn khi được đồng thời sửa đổi.

Để truy cập Danh sách đồng thời, Java cung cấp java.util.concurrent.CopyOnWriteArrayList.Sử dụng thực hiện này sẽ giải quyết vấn đề của bạn theo những cách khác nhau:

  • nó được đề an toàn, cho phép sửa đổi đồng thời
  • iterating trên ảnh chụp nhanh của danh sách không bị ảnh hưởng bởi các hoạt động bổ sung đồng thời, cho phép đồng thời bổ sung và lặp
  • đó là nhanh hơn so với đồng bộ hóa

Ngoài ra, nếu không sử dụng một bản sao của nội mảng là một yêu cầu nghiêm ngặt (mà tôi không thể tưởng tượng trong trường hợp của bạn, mảng này khá nhỏ vì nó chỉ chứa tham chiếu đối tượng, có thể được sao chép trong bộ nhớ khá hiệu quả), bạn có thể đồng bộ hóa quyền truy cập trên bản đồ. Nhưng điều đó sẽ yêu cầu Bản đồ được khởi tạo đúng cách, nếu không mã của bạn có thể ném NullPointerException vì thứ tự thực thi chuỗi không được đảm bảo (bạn giả sử rằng populateList() được bắt đầu trước đó, do đó danh sách sẽ được khởi tạo Khi sử dụng khối được đồng bộ hóa Trong trường hợp bạn có toàn bộ nội dung của phương thức run() trong khối đồng bộ, chuỗi trình đọc phải chờ cho đến khi kết quả từ con trỏ được xử lý - có thể mất một thời gian - vì vậy bạn thực sự mất tất cả đồng thời

Nếu bạn quyết định đi đến khối được đồng bộ hóa, tôi sẽ thực hiện các thay đổi sau (và tôi không tuyên bố, chúng hoàn toàn chính xác):

Khởi tạo các trường danh sách vì vậy chúng tôi có thể đồng bộ hóa truy cập vào nó:

private List<String> mList = new ArrayList<>(); //initialize the field 

Đồng bộ hóa các hoạt động sửa đổi (thêm). Không đọc dữ liệu từ con trỏ bên trong khối đồng bộ hóa vì nếu hoạt động của nó có độ trễ thấp, không thể đọc mList trong quá trình hoạt động đó, chặn tất cả các luồng khác trong một thời gian.

//mList = new ArrayList<>(); remove that line in your code 
String data = cursor.getString(cursor.getColumnIndex(DataProvider.NAME)); //do this before synchronized block! 
synchronized(mList){ 
    mList.add(data); 
} 

Các lặp đọc phải bên trong khối đồng bộ, vì vậy không có yếu tố được thêm vào, trong khi được lặp lại:

synchronized(mList){ 
    for (String name : mList) { 
    if (name.equals(query) { 
     return true; 
    } 
    } 
} 

Vì vậy, khi hai luồng hoạt động trên danh sách, một thread có thể thêm một một phần tử hoặc lặp lại toàn bộ danh sách tại một thời điểm. Bạn không thực thi song song trên các phần này của mã.

Về phiên bản được đồng bộ hóa của Danh sách (ví dụ: Vector, Collections.synchronizedList()). Những điều đó có thể kém hiệu quả hơn vì đồng bộ hóa bạn thực sự mất thực thi song song vì chỉ có một luồng có thể chạy các khối được bảo vệ tại một thời điểm. Hơn nữa, chúng vẫn có thể dễ bị ConcurrentModificationException, thậm chí có thể xảy ra trong một chuỗi. Nó được ném, nếu datastructure được sửa đổi giữa iterator creation và iterator nên tiến hành. Vì vậy, những cơ sở dữ liệu đó sẽ không giải quyết được vấn đề của bạn.

Tôi cũng không khuyến nghị sử dụng đồng bộ thủ công, vì nguy cơ làm sai chỉ là quá cao (đồng bộ hóa sai hoặc khác nhau, khối đồng bộ hóa quá lớn, ...)

TL; DR

sử dụng một java.util.concurrent.CopyOnWriteArrayList

+0

Tôi thích 'CopyOnWriteArrayList' hơn, nhưng OP cho biết" không sao chép ". Có thể anh ta muốn đảm bảo "chỉ một luồng có thể chạy các khối được bảo vệ cùng một lúc". – beatngu13

+2

"không sao chép" -requirement là loại vô nghĩa. Chắc chắn, các CopyOnWriteArrayList * không * sao chép nội dung, nhưng đó là một phần của các chi tiết thực hiện. Cách thay thế duy nhất là có một khối đồng bộ, đồng bộ hóa trên chính bản đồ, dễ bị lỗi khi nó được khởi tạo bằng 'null'. Sử dụng 'Vector' không ngăn cản' ConcurrentModificationException', vì nó không có gì để làm với concurrency (nó được ném khi một iterator cố gắng tiếp tục nhưng danh sách đã được sửa đổi sau khi tạo trình lặp) –

+0

Cảm ơn bạn đã giải trình. Tôi đoán 'CopyOnWriteArrayList' là tốt hơn cho trường hợp của tôi (kể từ khi tôi truy cập vào trường trên thread UI). – Nikolai

1

Bạn có thể sử dụng một Vector mà là tương đương với thread-safe của ArrayList.

EDIT: Nhờ Fildor's comment, bây giờ tôi biết điều này không tránh ConcurrentModificationException khỏi bị ném bằng nhiều đề:

Chỉ cuộc gọi duy nhất sẽ được đồng bộ hóa. Vì vậy, một trong những add không thể được gọi trong khi một thread được gọi thêm, ví dụ. Nhưng việc thay đổi danh sách sẽ khiến CME bị ném trong khi lặp lại trên một luồng khác. Bạn có thể đọc các tài liệu của trình vòng lặp về chủ đề đó.

Cũng thú vị:

câu chuyện dài ngắn: KHÔNG sử dụng Vector.

+0

Đơn giản chỉ cần sử dụng một Vector sẽ không tránh nhận được một ConcurrentModificationException. – Fildor

+0

@Fildor Nhưng chỉ khi cùng một luồng cố gắng lặp lại và sửa đổi, hoặc tôi có sai ở đây không? Tôi nghĩ rằng việc đồng bộ hóa ngăn chặn nhiều luồng truy cập vào cấu trúc dữ liệu cùng một lúc. – beatngu13

+2

Có, nhưng điều này không liên quan gì đến CME. Chỉ một cuộc gọi duy nhất sẽ được đồng bộ hóa. Vì vậy, một trong những add không thể được gọi trong khi một thread được gọi thêm, ví dụ. Nhưng việc thay đổi danh sách sẽ khiến CME bị ném trong khi lặp lại trên một luồng khác. Bạn có thể đọc các tài liệu của trình vòng lặp về chủ đề đó. Tôi tự mình bước vào cái bẫy đó - tôi đã học bằng cách đau đớn;) – Fildor

1

Sử dụng Collections.synchronizedList(new ArrayList<T>());

Ex:

Collections.synchronizedList(mList); 
+0

Sẽ không giúp chống lại sửa đổi đồng thời. – Fildor

1

java đồng bộ khối http://www.tutorialspoint.com/java/java_thread_synchronization.htm

class SomeClass { 

    private final Context mContext; 
    private List<String> mList = null; 

    SomeClass(Context context) { 
     mContext = context; 
    } 

    public void populateList() { 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       synchronized(SomeClass.this){ 
        mList = new ArrayList<>(); 

        Cursor cursor = mContext.getContentResolver().query(
          DataProvider.CONTENT_URI, null, null, null, null); 
        try { 
         while (cursor.moveToNext()) { 
          mList.add(cursor.getString(cursor.getColumnIndex(DataProvider.NAME))); 
         } 
        } catch (Exception e) { 
         Log.e("Error", e.getMessage(), e); 
        } finally { 
         if (cursor != null) { 
          cursor.close(); 
         } 
        } 
       } 
      } 
     }).start(); 
    } 

    public boolean searchList(String query) { // Invoked on the main thread 
    synchronized(SomeClass.this){ 
      if (mList != null) { 
       for (String name : mList) { 
        if (name.equals(query) { 
         return true; 
        } 
       } 
      } 

      return false; 
     } 
    } 
} 
+0

bạn có chắc chắn, các khối được đồng bộ hóa trên cùng một màn hình không? –

+0

bạn đang ở trên runnable nên có điểm chính xác cho class.this đối tượng bây giờ (sau khi cập nhật) Tôi khá chắc chắn nó nên làm việc –

+1

và đưa toàn bộ run() phương pháp vào một khối đồng bộ sẽ chặn truy cập trong một thời gian ... giả sử bạn lặp lại trên 1000 phần tử, với mỗi phần tử yêu cầu 1 giây để truy xuất, ứng dụng không phản hồi cho ~ 16min –

Các vấn đề liên quan