2010-03-09 42 views
11

Làm thế nào tôi có thể nhanh chóng chứng minh rằng lớp sau không an toàn cho luồng (vì nó sử dụng Khởi tạo Lười biếng và không sử dụng đồng bộ hóa) bằng cách viết một số mã? Nói cách khác, nếu tôi đang thử nghiệm lớp sau cho an toàn luồng, làm thế nào tôi có thể thất bại?Chứng minh mã sau không an toàn

public class LazyInitRace { 
    private ExpensiveObject instance = null; 

    public ExpensiveObject getInstance() { 
    if (instance == null) 
     instance = new ExpensiveObject(); 
    return instance; 
    } 
} 
+0

Sẽ thêm 'Thread.sleep' vào phần trợ giúp của hàm tạo? – Amarghosh

+2

Bạn có * để chứng minh nó bằng cách sử dụng mã không, hoặc bạn có thể chứng minh nó theo những cách khác không? Một biểu đồ thực hiện đơn giản có thể được sử dụng để chứng minh nó không an toàn. – Herms

+0

Có gì để chứng minh? Đây không phải là chủ đề an toàn. – ChaosPandion

Trả lời

1

Vâng ... Kết quả của mã này sẽ là sai, nơi bạn mong đợi cho một sự thật.

import java.util.concurrent.Callable; 
import java.util.concurrent.ExecutionException; 
import java.util.concurrent.FutureTask; 

public class LazyInitRace { 

    public class ExpensiveObject { 
     public ExpensiveObject() { 
      try { 
       Thread.sleep(1000); 
      } catch (InterruptedException e) { 
      } 
     } 
    } 

    private ExpensiveObject instance = null; 

    public ExpensiveObject getInstance() { 
     if (instance == null) 
      instance = new ExpensiveObject(); 
     return instance; 
    } 

    public static void main(String[] args) { 
     final LazyInitRace lazyInitRace = new LazyInitRace(); 

     FutureTask<ExpensiveObject> target1 = new FutureTask<ExpensiveObject>(
       new Callable<ExpensiveObject>() { 

        @Override 
        public ExpensiveObject call() throws Exception { 
         return lazyInitRace.getInstance(); 
        } 
       }); 
     new Thread(target1).start(); 

     FutureTask<ExpensiveObject> target2 = new FutureTask<ExpensiveObject>(
       new Callable<ExpensiveObject>() { 

        @Override 
        public ExpensiveObject call() throws Exception { 
         return lazyInitRace.getInstance(); 
        } 
       }); 
     new Thread(target2).start(); 

     try { 
      System.out.println(target1.get() == target2.get()); 
     } catch (InterruptedException e) { 
     } catch (ExecutionException e) { 
     } 
    } 
} 
+0

Nếu tôi bỏ qua Thread.sleep() trong hàm tạo, thì nó trả về true. Có lẽ nếu tôi chạy nó hàng ngàn lần, đôi khi tôi sẽ bị lừa dối. – portoalet

12

Bạn có thể buộc ExpensiveObject mất nhiều thời gian để xây dựng trong thử nghiệm không? Nếu vậy, chỉ cần gọi getInstance() hai lần từ hai luồng khác nhau, trong một khoảng thời gian đủ ngắn mà hàm tạo đầu tiên sẽ không hoàn thành trước khi thực hiện cuộc gọi thứ hai. Bạn sẽ kết thúc với hai trường hợp khác nhau đang được xây dựng, đó là nơi bạn nên thất bại.

Làm cho khóa kiểm tra hai lần không được kiểm tra sẽ khó hơn, hãy nhớ bạn ... (mặc dù không an toàn nếu không chỉ định volatile cho biến).

+1

@Jon Skeet: Hãy tha thứ cho sự thiếu hiểu biết của tôi, nhưng tôi muốn biết ý nghĩa cụ thể của dễ bay hơi? –

+1

dễ bay hơi là một từ khóa java được sử dụng để kiểm soát cách jvm xử lý truy cập bộ nhớ giữa các luồng. –

-1

Vâng, nó không phải là chủ đề an toàn. Proofing của chủ đề an toàn là ngẫu nhiên mà là đơn giản:

  1. Hãy ExpensiveObject constructor hoàn toàn an toàn:

    đồng bộ ExpensiveObject() {...

  2. Nơi để xây dựng mã mà kiểm tra nếu một bản sao khác của đối tượng tồn tại - sau đó tăng ngoại lệ.

  3. Tạo chủ đề phương pháp an toàn để xóa 'dụ' biến

  4. Nơi mã tuần tự của getInstance/clearInstance để lặp để thực hiện bởi nhiều chủ đề và chờ ngoại lệ từ (2)

+0

Tôi không nghĩ rằng anh ta đang yêu cầu các phương pháp để * làm cho * chuỗi mã an toàn, nhưng đối với một cách để * hiển thị * rằng nó không phải là. –

+0

@Michael Borgwardt nhìn vào (2) - việc nâng cao ngoại lệ là SHOW không an toàn. Tất cả các thủ thuật đồng bộ khác mà tôi đã sử dụng chỉ để tránh giới thiệu vấn đề mới trong mã đã tồn tại. – Dewfy

+0

Số 1 của bạn không hợp lệ, nó sẽ không biên dịch. "được đồng bộ hóa" không phải là công cụ sửa đổi hợp lệ cho một hàm tạo. Tất cả các nhà xây dựng vốn là chủ đề an toàn. Bên cạnh đó, giải pháp đề xuất của bạn quá phức tạp ... chỉ cần làm cho phương thức "getInstance" tĩnh được đồng bộ hóa. – NateS

14

Theo định nghĩa , điều kiện chủng tộc không thể được kiểm tra xác định, trừ khi bạn kiểm soát bộ lập lịch trình (mà bạn không). Điều gần nhất bạn có thể làm là thêm độ trễ có thể định cấu hình theo phương thức getInstance() hoặc viết mã nơi có sự cố có thể hiển thị và chạy hàng nghìn lần trong một vòng lặp.

BTW, không có điều nào trong số này thực sự tạo thành "bằng chứng". Formal Verification sẽ, nhưng rất, rất khó làm, ngay cả đối với số lượng mã tương đối nhỏ.

+5

+1 để sử dụng đúng từ "bằng chứng" – Seth

+3

Bất kỳ minh chứng nào cho thấy nó không thành công * chứng minh rằng nó có thể thất bại, vì vậy tôi không thực sự thấy bất kỳ vấn đề nào với việc sử dụng "bằng chứng" ở đây. – Herms

+2

đã đồng ý. bằng chứng bằng counterexample là bằng chứng hợp lệ. –

0

Đặt một tính toán rất dài trong constructor:

public ExpensiveObject() 
{ 
    for(double i = 0.0; i < Double.MAX_VALUE; ++i) 
    { 
     Math.pow(2.0,i); 
    } 
} 

Bạn có thể muốn giảm điều kiện chấm dứt để Double.MAX_VALUE/2.0 hoặc chia cho một số lượng lớn nếu MAX_VALUE là dùng quá lâu cho ý thích của bạn.

3

Vì đây là Java, bạn có thể sử dụng thư viện thread-weaver để tạm dừng hoặc ngắt mã và kiểm soát nhiều chuỗi thực thi. Bằng cách này bạn có thể nhận được một hàm tạo chậm ExpensiveObject mà không cần phải sửa đổi mã của hàm tạo, như cách khác đã đề xuất (chính xác).

+0

Chưa bao giờ nghe nói về thư viện đó trước đây. Trông khá thú vị. – Herms

5

Điều này không sử dụng mã, nhưng đây là ví dụ về cách tôi chứng minh điều đó.Tôi quên định dạng chuẩn cho các sơ đồ thực hiện như thế này, nhưng ý nghĩa phải đủ rõ ràng.

| Thread 1    | Thread 2    | 
|-----------------------|-----------------------| 
| **start**    |      | 
| getInstance()   |      | 
| if(instance == null) |      | 
| new ExpensiveObject() |      | 
| **context switch ->** | **start**    | 
|      | getInstance()   | 
|      | if(instance == null) | //instance hasn't been assigned, so this check doesn't do what you want 
|      | new ExpensiveObject() | 
| **start**    | **<- context switch** | 
| instance = result  |      | 
| **context switch ->** | **start**    | 
|      | instance = result  | 
|      | return instance  | 
| **start**    | **<- context switch** | 
| return instance  |      | 
0

Bạn có thể chứng minh điều đó dễ dàng với trình gỡ lỗi.

  1. Viết chương trình gọi getInstance() trên hai chủ đề riêng biệt .
  2. Đặt điểm ngắt trên công trình xây dựng của ExpensiveObject. Đảm bảo rằng trình gỡ lỗi sẽ chỉ tạm dừng chủ đề chứ không phải máy ảo.
  3. Sau đó, khi chuỗi đầu tiên dừng lại trên điểm ngắt, hãy để nó dừng lại.
  4. Khi chuỗi thứ hai dừng lại, bạn chỉ cần tiếp tục.
  5. Nếu bạn kiểm tra kết quả cuộc gọi getInstance() cho cả hai chủ đề, , chúng sẽ đề cập đến các trường hợp khác nhau.

Lợi thế khi thực hiện theo cách này, là bạn không thực sự cần ExpensiveObject, bất kỳ đối tượng nào trên thực tế cũng sẽ tạo ra kết quả tương tự. Bạn chỉ đơn giản là sử dụng trình gỡ rối để lên lịch thực hiện dòng mã cụ thể đó và do đó tạo ra một kết quả xác định.

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