2011-11-30 18 views
6

Tôi có một giao diện chung cho một số triển khai singleton. Giao diện định nghĩa phương thức khởi tạo có thể ném ngoại lệ đã kiểm tra.Nhà máy đối tượng singleton: mã này có an toàn không?

Tôi cần một nhà máy sẽ trả về các bản triển khai singleton được lưu trong bộ nhớ cache theo yêu cầu và tự hỏi liệu phương pháp tiếp cận sau có an toàn chỉ luồng không?

UPDATE1: Xin đừng đề nghị bất kỳ thư viện phần thứ 3, vì điều này sẽ đòi hỏi để có được giải phóng mặt bằng pháp lý do các vấn đề cấp phép có thể :-)

UPDATE2: mã này sẽ có khả năng được sử dụng trong EJB môi trường, do đó, nó thích hợp hơn không để sinh ra các chủ đề bổ sung hoặc sử dụng các công cụ như thế.

interface Singleton 
{ 
    void init() throws SingletonException; 
} 

public class SingletonFactory 
{ 
    private static ConcurrentMap<String, AtomicReference<? extends Singleton>> CACHE = 
     new ConcurrentHashMap<String, AtomicReference<? extends Singleton>>(); 

    public static <T extends Singleton> T getSingletonInstance(Class<T> clazz) 
     throws SingletonException 
    { 
     String key = clazz.getName(); 
     if (CACHE.containsKey(key)) 
     { 
      return readEventually(key); 
     } 

     AtomicReference<T> ref = new AtomicReference<T>(null); 
     if (CACHE.putIfAbsent(key, ref) == null) 
     { 
      try 
      { 
       T instance = clazz.newInstance(); 
       instance.init(); 
       ref.set(instance); // ----- (1) ----- 
       return instance; 
      } 
      catch (Exception e) 
      { 
       throw new SingletonException(e); 
      } 
     } 

     return readEventually(key); 
    } 

    @SuppressWarnings("unchecked") 
    private static <T extends Singleton> T readEventually(String key) 
    { 
     T instance = null; 
     AtomicReference<T> ref = (AtomicReference<T>) CACHE.get(key); 
     do 
     { 
      instance = ref.get(); // ----- (2) ----- 
     } 
     while (instance == null); 
     return instance; 
    } 
} 

Tôi không hoàn toàn chắc chắn về các dòng (1) và (2). Tôi biết rằng đối tượng được tham chiếu được khai báo là trường biến động trong AtomicReference và do đó các thay đổi được thực hiện tại dòng (1) sẽ hiển thị ngay lập tức tại dòng (2) - nhưng vẫn còn một số nghi ngờ ...

Khác hơn thế - tôi nghĩ sử dụng ConcurrentHashMap giải quyết nguyên tử của việc đưa khóa mới vào bộ nhớ cache.

Các bạn có thấy bất kỳ mối quan tâm nào với phương pháp này không? Cảm ơn!

PS: tôi biết về tĩnh lớp người giữ thành ngữ - và tôi không sử dụng nó do ExceptionInInitializerError (mà bất kỳ ngoại lệ ném trong instantiation singleton được gói vào) và tiếp theo NoClassDefFoundError mà không phải là điều mà tôi muốn bắt . Thay vào đó, tôi muốn tận dụng lợi thế của ngoại lệ kiểm tra chuyên dụng bằng cách bắt nó và xử lý nó một cách duyên dáng hơn là phân tích cú pháp dấu vết ngăn xếp của EIIR hoặc NCDFE.

Trả lời

0

Cân nhắc sử dụng số CacheBuilder của ổi. Ví dụ:

private static Cache<Class<? extends Singleton>, Singleton> singletons = CacheBuilder.newBuilder() 
    .build(
     new CacheLoader<Class<? extends Singleton>, Singleton>() { 
     public Singleton load(Class<? extends Singleton> key) throws SingletonException { 
      try { 
      Singleton singleton = key.newInstance(); 
      singleton.init(); 
      return singleton; 
      } 
      catch (SingletonException se) { 
      throw se; 
      } 
      catch (Exception e) { 
      throw new SingletonException(e); 
      } 
     } 
     }); 

public static <T extends Singleton> T getSingletonInstance(Class<T> clazz) { 
    return (T)singletons.get(clazz); 
} 

Lưu ý: ví dụ này chưa được kiểm tra và chưa được biên soạn.

Thực thi cơ bản Cache của Ổi sẽ xử lý tất cả bộ nhớ đệm và logic đồng thời cho bạn.

+0

Cảm ơn! Lib bên thứ 3 không phải là một lựa chọn trong trường hợp của tôi ... – anenvyguest

-1

Mã thường không an toàn vì có khoảng cách giữa séc CACHE.containsKey(key) và cuộc gọi CACHE.putIfAbsent(key, ref). Có thể cho hai luồng để gọi đồng thời vào phương thức (đặc biệt là trên các hệ thống đa lõi/bộ xử lý) và cả hai thực hiện kiểm tra containsKey(), sau đó cả hai cố gắng thực hiện các hoạt động đặt và tạo.

Tôi sẽ bảo vệ thực thi phương pháp getSingletonInstnace() bằng cách sử dụng khóa hoặc đồng bộ hóa trên màn hình của một số loại.

+1

Ông bảo vệ chống lại điều này bằng cách đặt một 'AtomicReference' với một giá trị' null'. Nếu 'putIfAbsent()' không trả về null, nó chỉ làm giảm nó và các cuộc gọi nhận được. Đó là điểm của vòng lặp. – Gray

3

Có tất cả những điều đồng thời/nguyên tử có thể gây ra các vấn đề khóa hơn chỉ cần đặt

synchronized(clazz){} 

khối xung quanh getter. Tài liệu tham khảo nguyên tử là dành cho các tham chiếu được CẬP NHẬT và bạn không muốn va chạm. Ở đây bạn có một nhà văn duy nhất, vì vậy bạn không quan tâm về điều đó.

Bạn có thể tối ưu hóa nó hơn nữa bằng việc có một hashmap, và chỉ khi có một bỏ lỡ, sử dụng các khối đồng bộ:

public static <T> T get(Class<T> cls){ 
    // No lock try 
    T ref = cache.get(cls); 
    if(ref != null){ 
     return ref; 
    } 
    // Miss, so use create lock 
    synchronized(cls){ // singletons are double created 
     synchronized(cache){ // Prevent table rebuild/transfer contentions -- RARE 
      // Double check create if lock backed up 
      ref = cache.get(cls); 
      if(ref == null){ 
       ref = cls.newInstance(); 
       cache.put(cls,ref); 
      } 
      return ref; 
     } 
    } 
} 
+0

Cảm ơn! Tôi đã xem xét cách tiếp cận này, nhưng có một lý do trong cuốn sách "Java Concurrency in Practice", rằng các thuật toán dựa trên nguyên tử dưới tải vừa phải tốt hơn các thuật toán dựa trên các khóa khóa, thay vào đó làm tốt hơn các khóa dựa trên nội tại. Tuy nhiên, tôi nghiêng về phía mã bạn đề nghị do nó dễ đọc hơn :-) – anenvyguest

+0

Có, nhưng như một singleton, bạn sẽ không bao giờ nhấn LOAD, bởi vì việc tạo ra là phần bị khóa duy nhất. Các getters đều không đồng bộ. Và theo kinh nghiệm của tôi, việc kéo sợi luôn tệ hơn khóa chặn. – Nthalk

+0

Ngoài nhận xét trước của tôi - tôi nghĩ rằng java.util.HashMap đơn giản sẽ không đủ ở đây vì 'cache.put (cls, ref)' có thể kích hoạt xây dựng lại bảng nội bộ và do đó 'cache.get (cls)' có thể xem bản đồ ở trạng thái không ổn định, vì cuộc gọi thứ hai được định dạng outisde của khối 'synchronized'. – anenvyguest

0

này có vẻ như nó sẽ làm việc mặc dù tôi có thể xem xét một số loại giấc ngủ nếu ngay cả một nano giây hoặc một cái gì đó khi thử nghiệm cho tham chiếu được thiết lập. Vòng quay thử nghiệm sẽ cực kỳ tốn kém.

Ngoài ra, tôi sẽ xem xét cải thiện mã bằng cách chuyển số AtomicReference đến readEventually() để bạn có thể tránh các điều kiện chủng tộc containsKey() và sau đó putIfAbsent(). Vì vậy, các mã sẽ là:

AtomicReference<T> ref = (AtomicReference<T>) CACHE.get(key); 
if (ref != null) { 
    return readEventually(ref); 
} 

AtomicReference<T> newRef = new AtomicReference<T>(null); 
AtomicReference<T> oldRef = CACHE.putIfAbsent(key, newRef); 
if (oldRef != null) { 
    return readEventually(oldRef); 
} 
... 
3

Bạn đã đi đến rất nhiều công việc để tránh đồng bộ, và tôi cho rằng lý do để làm điều này là dành cho mối quan tâm thực hiện. Bạn đã thử nghiệm để xem liệu điều này có thực sự cải thiện hiệu suất so với giải pháp được đồng bộ hóa không?

Lý do tôi hỏi là các lớp đồng thời có xu hướng chậm hơn so với các lớp không đồng thời, chưa kể đến cấp độ chuyển hướng bổ sung với tham chiếu nguyên tử. Tùy thuộc vào tranh chấp chủ đề của bạn, một giải pháp đồng bộ ngây thơ thực sự có thể nhanh hơn (và dễ dàng hơn để xác minh tính chính xác).

Ngoài ra, tôi nghĩ rằng bạn có thể có thể kết thúc với một vòng lặp vô hạn khi một SingletonException được ném trong một cuộc gọi đến instance.init(). Lý do là một thread đồng thời chờ đợi trong readEventually sẽ không bao giờ kết thúc việc tìm kiếm thể hiện của nó (vì một ngoại lệ đã được ném trong khi một luồng khác đã khởi tạo thể hiện). Có lẽ đây là hành vi đúng cho trường hợp của bạn, hoặc có thể bạn muốn đặt một số giá trị đặc biệt cho cá thể để kích hoạt một ngoại lệ được ném vào chuỗi chờ đợi.

+0

Bắt tốt!Tôi thực sự bỏ lỡ vòng lặp vô hạn khi ngoại lệ được ném. – anenvyguest

-1

google "Trình ghi nhớ". về cơ bản, thay vì AtomicReference, hãy sử dụng Future.

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