2009-07-01 36 views
5

Khi chạy lớp sau ExecutionService thường sẽ bế tắc.Tại sao ExecutorService bế tắc khi thực hiện các thao tác HashMap?

import java.util.ArrayList; 
import java.util.Collection; 
import java.util.HashMap; 
import java.util.Iterator; 
import java.util.concurrent.Callable; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 


public class ExecutorTest { 
public static void main(final String[] args) throws InterruptedException { 
    final ExecutorService executor = Executors.newFixedThreadPool(10); 

    final HashMap<Object, Object> map = new HashMap<Object, Object>(); 
    final Collection<Callable<Object>> actions = new ArrayList<Callable<Object>>(); 
    int i = 0; 
    while (i++ < 1000) { 
     final Object o = new Object(); 
     actions.add(new Callable<Object>() { 
      public Object call() throws Exception { 
       map.put(o, o); 
       return null; 
      } 
     }); 
     actions.add(new Callable<Object>() { 
      public Object call() throws Exception { 
       map.put(new Object(), o); 
       return null; 
      } 
     }); 
     actions.add(new Callable<Object>() { 
      public Object call() throws Exception { 
       for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { 
        iterator.next(); 
       } 
       return null; 
      } 
     }); 
    } 
    executor.invokeAll(actions); 
    System.exit(0); 
} 

} 

Vậy tại sao điều này lại xảy ra? Hoặc tốt hơn - làm thế nào tôi có thể viết một bài kiểm tra để đảm bảo rằng việc triển khai bản đồ trừu tượng tùy chỉnh là luồng an toàn? (Một số triển khai có nhiều bản đồ, một đại biểu khác thực hiện bộ nhớ cache, v.v.)

Một số nền: điều này xảy ra trong Java 1.6.0_04 và 1.6.0_07 trên Windows. Tôi biết rằng vấn đề xuất phát từ sun.misc.Unsafe.park():

  • tôi có thể tạo lại vấn đề trên máy tính xách tay Core2 Duo 2.4Ghz của tôi, nhưng không phải trong khi đang chạy trong debug
  • Tôi có thể gỡ lỗi trên Core2 của tôi Quad tại nơi làm việc, nhưng tôi đã treo nó qua RDP, như vậy sẽ không thể có được một chồng dấu vết đến ngày mai

Hầu hết các câu trả lời dưới đây là về sự an toàn phi chủ đề của HashMap, nhưng tôi có thể tìm không có chủ đề bị khóa trong HashMap - nó là tất cả trong mã ExecutionService (và Unsafe.park()). Tôi sẽ kiểm tra chặt chẽ các chủ đề vào ngày mai.

Tất cả điều này vì triển khai Bản đồ trừu tượng tùy chỉnh không an toàn chỉ vì vậy tôi đặt đảm bảo rằng tất cả các triển khai sẽ an toàn chỉ. Về bản chất, tôi muốn đảm bảo rằng sự hiểu biết của tôi về ConcurrentHashMap vv là chính xác những gì tôi mong đợi, nhưng đã tìm thấy ExecutionService để được kỳ lạ thiếu ...

+0

hành động hiện tại của tôi cho ngày mai: 1. Kiểm tra chặt chẽ để đề dừng lại ở HashMap [resize] code 2. Nâng cấp lên vm mới nhất – Stephen

+0

Bạn sẽ không nhận được bất kỳ đề dừng lại thay đổi kích thước - họ sẽ thay đổi kích thước và thoát. Kiểm tra thread bị mắc kẹt trong đó iterating iterable Callable. –

Trả lời

16

Bạn đang sử dụng một nổi tiếng không-thread-safe và phàn nàn về bế tắc. Tôi không thấy vấn đề ở đây là gì.

Ngoài ra, làm thế nào là ExecutionService

strangely lacking 

?

Một quan niệm sai lầm phổ biến là sử dụng ví dụ: a HashMap bạn sẽ tận dụng tối đa một số dữ liệu cũ. Xem a beautiful race condition về cách bạn có thể thổi phồng JVM của mình bằng cách thực hiện điều đó.

Hiểu tại sao điều này xảy ra là một quá trình rất phức tạp và đòi hỏi kiến ​​thức về nội bộ của cả JVM lẫn các thư viện lớp.

Đối với ConcurrentHashMap, chỉ cần đọc javadoc - nó sẽ làm rõ câu hỏi của bạn. Nếu không, hãy xem Java Concurrency in Practice.


Cập nhật:

tôi quản lý để tái tạo tình hình của bạn, nhưng nó không phải là một bế tắc. Một trong số actions không bao giờ hoàn thành việc thực thi. Stack trace là:

"pool-1-thread-3" prio=10 tid=0x08110000 nid=0x22f8 runnable [0x805b0000] 
java.lang.Thread.State: RUNNABLE 
at ExecutorTest$3.call(ExecutorTest.java:36) 
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) 
at java.util.concurrent.FutureTask.run(FutureTask.java:138) 
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) 
at java.lang.Thread.run(Thread.java:619) 

Dường như trường hợp chính xác tôi liên kết với - HashMap được thay đổi kích cỡ và do các cơ chế nội bộ thay đổi kích thước iterator bị mắc kẹt trong một vòng lặp vô hạn.

Khi điều này xảy ra, invokeAll không bao giờ trả lại và chương trình bị treo.Nhưng đó không phải là bế tắc, cũng không phải là trò chơi trực tiếp, nhưng là điều kiện đua xe .

+0

Tôi hiểu điều đó (tôi nghĩ - tôi sẽ đọc về điều kiện chủng tộc). Vấn đề là tôi thấy không có chủ đề thực sự bị khóa trong mã HashMap. Đó là tất cả trong chức năng xếp hàng ExecutionService. ví dụ. Unsafe.park() như đã nêu. – Stephen

+0

Tôi không nhận được deadlocks trên Core 2 của tôi bằng cách sử dụng 6u14 vì vậy tôi không thể nói bất cứ điều gì về điều đó. Bạn có thể muốn thử sử dụng ConcurrentHashMap chỉ để xem liệu bế tắc có còn lại không. Nhưng tôi không thấy lý do bế tắc ở đây. –

+0

@Stephen - Tôi đã xoay sở để tái tạo và thêm giải thích. –

2

Bạn hiểu gì về bế tắc?

Có ít nhất hai vấn đề với mã. Các HashMap được sử dụng từ nhiều chủ đề cùng một lúc và do đó có thể nhận được vào một vòng lặp vô hạn. Bạn đang lặp lại bộ mục nhập trong khi có khả năng thay đổi cấu trúc dữ liệu cơ bản (ngay cả khi mỗi thao tác riêng lẻ được đồng bộ hóa hasNext/next sẽ không là nguyên tử).

Cũng lưu ý rằng các phiên bản 1.6.0 được cập nhật với Bản phát hành bảo mật Synhronized mới nhất (SSR) là 1.6.0_13 và 1.6.0_14.

+0

Bạn có thể cung cấp liên kết đến chi tiết về những gì có trong bản phát hành SSR không? – pjp

+0

@pjp Bây giờ chúng được biết là CPU đặc biệt (Bản cập nhật Patch quan trọng, đặc biệt vì chúng hiện không đồng bộ với các CPU quaterly của Oracle). Đây là liên kết đến tư vấn mới nhất với ma trận rủi ro tại thời điểm viết: http://www.oracle.com/technetwork/topics/security/javacpujune2011-313339.html#AppendixJAVA Không có thêm chi tiết nào được đưa ra. –

0

Xét về làm công việc kiểm tra - thay vì:

executor.invokeAll(actions); 

sử dụng

executor.invokeAll(actions, 2, TimeUnit.SECONDS); 

cũng lưu ý rằng để làm bài kiểm tra thực sự làm việc (và báo cáo lỗi), bạn sẽ cần phải làm điều gì đó như:

List<Future> results = executor.invokeAll(actions, 2, TimeUnit.SECONDS); 
executor.shutdown(); 
for (Future result : results) { 
    result.get(); // This will report the exceptions encountered when executing the action ... the ConcurrentModificationException I wanted in this case (or CancellationException in the case of a time out) 
} 
//If we get here, the test is successful... 
1

Tôi tin rằng bản đồ của bạn đang được sửa đổi đồng thời. Nếu put() được gọi trong khi hành động lặp của bạn đang được tiến hành, trong một số điều kiện nhất định (đặc biệt nếu thay đổi kích thước xảy ra), bạn có thể kết thúc trong một vòng lặp vô hạn. Đây là một hành vi khá nổi tiếng (xem here).

Vòng bế tắc và vòng lặp vô hạn sẽ tự hiển thị rất khác nhau. Nếu bạn có một bế tắc thực sự, kết xuất chuỗi sẽ hiển thị rõ ràng các chủ đề lồng vào nhau. Mặt khác, một khi bạn đã vào một vòng lặp vô hạn, CPU của bạn sẽ tăng đột biến cao và ngăn xếp dấu vết sẽ thay đổi mỗi khi bạn lấy bãi chứa.

Điều này không liên quan gì đến Người thi hành và mọi việc cần làm với sử dụng đồng thời không an toàn HashMap chưa bao giờ được thiết kế để sử dụng theo cách đó. Trong thực tế nó là khá dễ dàng để tái sản xuất vấn đề này với một loạt các số ít các chủ đề.

Giải pháp tốt nhất cho việc này là chuyển sang ConcurrentHashMap. Nếu bạn chuyển sang HashMap đã đồng bộ hóa hoặc Hashtable, bạn sẽ không nhận được một vòng lặp vô hạn, nhưng bạn vẫn có thể nhận được ConcurrentModificationExceptions trong khi lặp lại.

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