2009-09-17 37 views
5

Hôm nay, tôi phát hiện ra rằng việc sử dụng Collections.synchronizedXXX không hoạt động tốt với sự phản chiếu.Cách giải quyết cho trình bao bọc Bộ sưu tập Java phá vỡ sự phản chiếu

Dưới đây là một ví dụ đơn giản:

import java.util.ArrayList; 
import java.util.Collections; 
import java.util.List; 

public class Weird{ 
    public static void main(String[] args) { 
    List<String> list = new ArrayList<String>(); 
    list.add("Hello World"); 

    List<String> wrappedList = Collections.synchronizedList(list); 

    printSizeUsingReflection(list); 
    printSizeUsingReflection(wrappedList); 
    } 

    private static void printSizeUsingReflection(List<String> list) { 
    try { 
     System.out.println(
      "size = " + list.getClass().getMethod("size").invoke(list)); 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
    } 
} 

Cuộc gọi đầu tiên printSizeUsingReflection in kích thước (ví dụ "1"), kết quả cuộc gọi thứ hai trong:

java.lang.IllegalAccessException: Class Weird can not access a member of class 
    java.util.Collections$SynchronizedCollection with modifiers "public" 
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65) 
    at java.lang.reflect.Method.invoke(Method.java:588) 
    at Weird.printSizeUsingReflection(Weird.java:18) 
    at Weird.main(Weird.java:13) 

Đây là một chút ngạc nhiên và làm phiền. Có cách giải quyết tốt không? Tôi biết có một thực thi List-safe thread trong java.util.concurrent, nhưng việc thực hiện đó có vẻ chậm hơn so với việc sử dụng Collections.synchronizedList().

Trả lời

5

Lý do cho sự ngoại lệ được ném là lớp thu được bằng cách invocation

list.getClass() 

trong dòng

System.out.println("size = " + list.getClass().getMethod("size").invoke(list)); 

trả về kiểu thực tế - java.util.Collections $ SynchronizedCollection. Lớp SynchronizedCollection xảy ra là một lớp bên trong của lớp Collections, mà bạn không thể truy cập, do đó ngoại lệ. Cách tốt nhất để bỏ qua ngoại lệ này là gọi phương thức kích thước trên một lớp/giao diện phù hợp hơn - thường điều này xảy ra là lớp triển khai (vì chắc chắn sẽ chứa khai báo hoặc định nghĩa phương thức), nhưng trong trường hợp của chúng ta chúng ta cần gọi kích thước() trên một kiểu công cộng cho rằng obj.getClass() đã trả về một kiểu không công khai. Để cụ thể, chúng ta cần gọi kích thước() trên giao diện java.util.Collection (siêu) hoặc giao diện java.util.List.

Các báo cáo sau đây sẽ in ra "size = 1" trong giao diện điều khiển:

System.out.println("size = " + Collection.class.getMethod("size").invoke(list)); 
System.out.println("size = " + List.class.getMethod("size").invoke(list)); 

Cập nhật

Trong trường hợp bạn đang tự hỏi liệu kích thước() phương pháp gọi thông qua phản ánh được đồng bộ hóa hoặc không, câu trả lời là có, nó được đồng bộ hóa. Mặc dù gọi phương thức size() trên kiểu siêu - Collection/List, phương thức size() trong lớp bên trong SynchronizedCollection được gọi và điều này xảy ra được đồng bộ. Đây là một chi tiết thực hiện mặc dù, đảm bảo để làm việc kể từ khi bộ sưu tập được bọc.

Ngoài ra, không nên sử dụng sự phản chiếu khi the supertype - the Collection interface contains the size() method.

3

Lấy phương thức từ Collection.class (tổng quát hơn lặp lại các lớp siêu (và giao diện) để tìm nội dung nào đó công khai). Hoặc chỉ không sử dụng sự phản chiếu.

+0

+1 chỉ không sử dụng phản chiếu. 'list.size()' hoạt động tốt trong các tình huống này. – akf

+0

Tôi muốn tránh sự phản chiếu nếu có thể. Trong trường hợp này, tôi có một trình chặn AOP được thiết lập bởi Google Guice và tôi đang cố gắng ghi lại những điều hữu ích về các đối số phương thức - không may, các kiểu lớp về cơ bản chỉ là tôi nghĩ về các đối số. Cảm ơn bạn về mẹo Collection.class .. hoạt động như một sự quyến rũ! –

0

Bạn có chắc chắn lựa chọn java.util.concurrent sẽ chậm hơn (hoặc, đủ chậm để lo lắng?).

Tôi đoán 2 lựa chọn của bạn có:

  1. LinkedBlockingQueue
  2. CopyOnWriteArrayList

Tôi nghĩ rằng đối với trường hợp sử dụng nhất định, những triển khai thực tế có thể nhanh. Và như một lưu ý phụ, nếu bạn cuối cùng cần nó, các bộ sưu tập trong java.util.concurrent có thể xử lý sửa đổi đồng thời.

Từ phần Concurrent Collections của "Java Concurrency in Practice":

CopyOnWriteArrayList là một thay thế đồng thời cho một danh sách đồng bộ cung cấp đồng thời tốt hơn trong một số tình huống thông thường và loại bỏ cần phải khóa hoặc sao chép bộ sưu tập trong khi lặp lại. (Tương tự như vậy, CopyOnWriteArraySet là một thay thế đồng thời cho một Set đồng bộ.)

1

java.util.Collections $ SynchronizedCollection là ngoài công lập lớp.

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