2012-11-15 42 views
10

Java đảm bảo đặc điểm kỹ thuật bài tập biến nguyên thủy luôn nguyên tử (mong đợi cho long và đôi types.Quan hệ giữa các hướng dẫn bytecode và các hoạt động xử lý

Ngược lại, Fetch-and-Add hoạt động tương ứng với hoạt động i++ increment nổi tiếng, sẽ là phi nguyên tử . vì dẫn đến một hoạt động đọc-sửa đổi-ghi

Giả sử mã này:

public void assign(int b) { 
    int a = b; 
} 

Các tạo bytecode là:

public void assign(int); 
    Code: 
     0: iload_1  
     1: istore_2  
     2: return 

Như vậy, chúng ta thấy sự phân công bao gồm hai bước (tải và lưu trữ).

Giả sử mã này:

public void assign(int b) { 
     int i = b++; 
} 

Bytecode:

public void assign(int); 
    Code: 
     0: iload_1  
     1: iinc   1, 1 //extra step here regarding the previous sample 
     4: istore_2  
     5: return 

Biết rằng bộ vi xử lý X86 có thể (ít nhất là những người hiện đại), hoạt động hoạt động tăng nguyên tử, như đã nói:

Trong khoa học máy tính, lệnh tìm nạp và thêm CPU là lệnh đặc biệt mà modifie nguyên tử nội dung của bộ nhớ vị trí. Nó được sử dụng để thực hiện loại trừ lẫn nhau và đồng thời các thuật toán trong các hệ thống đa xử lý, một khái quát hóa các ẩn dụ.

Như vậy, câu hỏi đầu tiên: Mặc dù thực tế là bytecode đòi hỏi cả hai bước (tải và lưu trữ), không Java dựa trên thực tế là hoạt động chuyển nhượng là một hoạt động luôn luôn thực hiện nguyên tử bất kỳ kiến ​​trúc của vi xử lý và do đó có thể đảm bảo nguyên tử vĩnh viễn (cho các bài tập nguyên thủy) trong đặc điểm kỹ thuật của nó?

Thứ hai câu hỏi: Có sai lầm khi khẳng định rằng, với bộ vi xử lý X86 rất hiện đại và không chia sẻ mã biên dịch trên kiến ​​trúc khác nhau, không có nhu cầu ở tất cả để đồng bộ hóa các i++ hoạt động (hoặc AtomicInteger)? Xem xét nó đã nguyên tử rồi.

+1

Sự hiểu biết của tôi là nguyên tử của phép gán chỉ có nghĩa là istore là nguyên tử - nói cách khác trong 'a = b', có thể là' b' được đọc, sau đó bị biến đổi thành giá trị mới thì giá trị ban đầu là được gán cho 'a'. Tuy nhiên, nguyên tử đảm bảo rằng 'a' sẽ không là một kết hợp của 2 giá trị mà' b' đã giữ. – assylias

+0

@assylias Đó là những gì tôi nghĩ, tôi đồng ý với quan điểm này :) – Mik378

Trả lời

4

Xem xét Câu hỏi thứ hai.

Bạn ngụ ý rằng i++ sẽ chuyển thành lệnh tìm nạp và thêm X86 không đúng. Nếu mã được biên dịch và tối ưu hóa bởi JVM, mã có thể là đúng (sẽ phải kiểm tra mã nguồn của JVM để xác nhận), nhưng mã đó cũng có thể chạy ở chế độ được giải thích, trong đó tìm nạpthêm được tách ra và không được đồng bộ hóa.

Ra khỏi tò mò tôi đã kiểm tra những gì mã lắp ráp được tạo ra cho mã Java này:

public class Main { 
    volatile int a; 

    static public final void main (String[] args) throws Exception { 
    new Main().run(); 
    } 

    private void run() { 
     for (int i = 0; i < 1000000; i++) { 
     increase(); 
     } 
    } 

    private void increase() { 
    a++; 
    } 
} 

tôi đã sử dụng Java HotSpot(TM) Server VM (17.0-b12-fastdebug) for windows-x86 JRE (1.6.0_20-ea-fastdebug-b02), built on Apr 1 2010 03:25:33 phiên bản của JVM (cái này tôi đã ở đâu đó trên ổ đĩa của tôi).

Những là sản phẩm quan trọng của chạy nó (java -server -XX:+PrintAssembly -cp . Main):

Lúc đầu, nó được biên dịch vào này:

00c  PUSHL EBP 
    SUB ESP,8 # Create frame 
013  MOV EBX,[ECX + #8] # int ! Field VolatileMain.a 
016  MEMBAR-acquire ! (empty encoding) 
016  MEMBAR-release ! (empty encoding) 
016  INC EBX 
017  MOV [ECX + #8],EBX ! Field VolatileMain.a 
01a  MEMBAR-volatile (unnecessary so empty encoding) 
01a  LOCK ADDL [ESP + #0], 0 ! membar_volatile 
01f  ADD ESP,8 # Destroy frame 
    POPL EBP 
    TEST PollPage,EAX ! Poll Safepoint 

029  RET 

Sau đó, nó được inlined và biên dịch thành này:

0a8 B11: # B11 B12 &lt;- B10 B11 Loop: B11-B11 inner stride: not constant post of N161 Freq: 0.999997 
0a8  MOV EBX,[ESI] # int ! Field VolatileMain.a 
0aa  MEMBAR-acquire ! (empty encoding) 
0aa  MEMBAR-release ! (empty encoding) 
0aa  INC EDI 
0ab  INC EBX 
0ac  MOV [ESI],EBX ! Field VolatileMain.a 
0ae  MEMBAR-volatile (unnecessary so empty encoding) 
0ae  LOCK ADDL [ESP + #0], 0 ! membar_volatile 
0b3  CMP EDI,#1000000 
0b9  Jl,s B11 # Loop end P=0.500000 C=126282.000000 

Như bạn có thể thấy, nó không sử dụng lệnh Fetch-And-Add cho a++.

+0

+1 Nếu spec không guarentee một hoạt động nguyên tử không có nghĩa là một sẽ không được sử dụng. BTW: Trong ví dụ trên, tôi sẽ giả định JIT sẽ tối ưu hóa phương thức này. ;) –

+0

@ShyJ Mẫu tuyệt vời, cảm ơn! – Mik378

1

Về câu hỏi đầu tiên của bạn: đọc và viết là nguyên tử, nhưng hoạt động đọc/ghi thì không. Tôi không thể tìm thấy một tài liệu tham khảo cụ thể về nguyên thủy nhưng JLS #17.7 nói điều gì đó tương tự như tài liệu tham khảo liên quan đến:

viết thư cho và đọc tài liệu tham khảo luôn nguyên tử, bất kể họ đang thực hiện như 32-bit hoặc 64-bit giá trị.

Vì vậy, trong trường hợp của bạn, cả iload và istore đều là nguyên tử, nhưng toàn bộ hoạt động (iload, istore) thì không.

Có sai không khi xem xét] không cần thiết phải đồng bộ hóa hoạt động i ++?

Về câu hỏi thứ hai của bạn, mã dưới đây in 982 trên máy tính của tôi x86 (và không phải 1000) trong đó cho thấy rằng một số ++ bị lạc trong dịch ==> bạn cần phải đồng bộ hóa đúng một hoạt động ++ ngay cả trên một kiến ​​trúc vi xử lý hỗ trợ lệnh tìm nạp và thêm.

public class Test1 { 

    private static int i = 0; 

    public static void main(String args[]) throws InterruptedException { 
     ExecutorService executor = Executors.newFixedThreadPool(10); 
     final CountDownLatch start = new CountDownLatch(1); 
     final Set<Integer> set = new ConcurrentSkipListSet<>(); 
     Runnable r = new Runnable() { 
      @Override 
      public void run() { 
       try { 
        start.await(); 
       } catch (InterruptedException ignore) {} 
       for (int j = 0; j < 100; j++) { 
        set.add(i++); 
       } 
      } 
     }; 

     for (int j = 0; j < 10; j++) { 
      executor.submit(r); 
     } 
     start.countDown(); 
     executor.shutdown(); 
     executor.awaitTermination(1, TimeUnit.SECONDS); 
     System.out.println(set.size()); 
    } 
} 
+1

Điều đó xác nhận những gì ShyJ đã tuyên bố => "Bạn ngụ ý rằng i + + sẽ dịch sang lệnh X86 Fetch-And-Add không đúng". – Mik378

+0

@ Mik378 đã thêm nội dung nào đó vào câu hỏi đầu tiên của bạn. – assylias

+0

Cảm ơn, giờ đã rõ ràng :) – Mik378

5

Thậm chí nếu i ++ sẽ chuyển thành một X86 Fetch-Và-Thêm hướng dẫn sẽ thay đổi gì bởi vì bộ nhớ mentionned trong hướng dẫn Fetch-Và-Thêm đề cập đến registres bộ nhớ cục bộ của CPU chứ không phải bộ nhớ chung của thiết bị/ứng dụng. Trên một CPU hiện đại, thuộc tính này sẽ mở rộng đến bộ nhớ cache cục bộ của CPU và thậm chí có thể mở rộng tới các bộ đệm khác nhau được sử dụng bởi các lõi khác nhau cho một CPU đa lõi nhưng trong trường hợp của một ứng dụng đa luồng; hoàn toàn không có bảo đảm rằng bản phân phối này sẽ mở rộng đến bản sao bộ nhớ được sử dụng bởi chính các chủ đề đó.

Rõ ràng, trong một ứng dụng đa luồng, nếu một biến có thể được sửa đổi bởi các luồng khác nhau chạy cùng lúc, bạn phải sử dụng một số đồng bộ hóa mecanism được cung cấp bởi hệ thống và bạn không thể dựa vào thực tế là lệnh i ++ chiếm một một dòng mã java là nguyên tử.