2009-03-18 29 views
25

thể trùng lặp:
What is the best way to increase number of locks in javaĐồng bộ hóa trên một giá trị Integer

Giả sử tôi muốn khóa dựa trên một giá trị số nguyên id. Trong trường hợp này, có một hàm kéo giá trị từ bộ nhớ cache và thực hiện việc truy xuất/lưu trữ khá tốn kém vào bộ đệm nếu giá trị không có.

Các mã hiện không đồng bộ và có khả năng có thể gây ra nhiều lấy/cửa hàng hoạt động:

//psuedocode 
public Page getPage (Integer id){ 
    Page p = cache.get(id); 
    if (p==null) 
    { 
     p=getFromDataBase(id); 
     cache.store(p); 
    } 
} 

Những gì tôi muốn làm là đồng bộ lấy trên id, ví dụ

if (p==null) 
    { 
     synchronized (id) 
     { 
     ..retrieve, store 
     } 
    } 

Thật không may điều này sẽ không hoạt động vì 2 cuộc gọi riêng biệt có thể có cùng giá trị id Integer nhưng đối tượng Integer khác nhau, vì vậy chúng sẽ không chia sẻ khóa và không đồng bộ hóa.

Có cách nào đơn giản để đảm bảo rằng bạn có cùng một phiên bản Integer không? Ví dụ, sẽ làm việc này:

syncrhonized (Integer.valueOf(id.intValue())){ 

Các javadoc cho Integer.valueOf() dường như ngụ ý rằng bạn có khả năng để có được những ví dụ tương tự, nhưng điều đó không trông giống như một bảo lãnh:

Trả về phiên bản Số nguyên biểu thị giá trị int được chỉ định. Nếu một trường hợp Integer mới không yêu cầu, phương pháp này nên thường được sử dụng trong ưu tiên cho constructor Integer (int), như phương pháp này có khả năng mang lại không gian tốt hơn đáng kể và thời gian hiệu suất của bộ nhớ đệm thường xuyên đã yêu cầu giá trị.

Vì vậy, bất kỳ đề xuất nào về cách nhận một phiên bản Integer được đảm bảo giống nhau, ngoài các giải pháp phức tạp hơn như giữ một đối tượng WeakHashMap of Lock được khóa vào int? (không có gì sai với điều đó, nó chỉ có vẻ như phải có một lớp lót rõ ràng hơn tôi đang mất tích).

+2

Câu hỏi "trùng lặp" này và câu trả lời của Eddie bên dưới tốt hơn câu hỏi "gốc"! – arun

+0

Xem thêm http://stackoverflow.com/questions/6616141/java-threads-locking-on-a-specific-object cụ thể http://stackoverflow.com/a/27806218/32453 – rogerdpack

+0

Điều gì về việc sử dụng [AtomicInteger] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html)? – GregT

Trả lời

43

Bạn thực sự không muốn đồng bộ hóa trên Integer, vì bạn không có quyền kiểm soát đối với những trường hợp nào giống nhau và trường hợp nào khác nhau. Java chỉ không cung cấp một cơ sở như vậy (trừ khi bạn đang sử dụng các số nguyên trong một phạm vi nhỏ) mà là đáng tin cậy trên các JVM khác nhau. Nếu bạn thực sự phải đồng bộ hóa trên một Integer, sau đó bạn cần giữ một Map hoặc Set of Integer để bạn có thể đảm bảo rằng bạn đang nhận được bản sao chính xác mà bạn muốn.

Tốt hơn là tạo đối tượng mới, có thể được lưu trữ trong HashMap được khóa bởi Integer, để đồng bộ hóa. Một cái gì đó như thế này:

public Page getPage(Integer id) { 
    Page p = cache.get(id); 
    if (p == null) { 
    synchronized (getCacheSyncObject(id)) { 
     p = getFromDataBase(id); 
     cache.store(p); 
    } 
    } 
} 

private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>(); 

private Object getCacheSyncObject(final Integer id) { 
    locks.putIfAbsent(id, id); 
    return locks.get(id); 
} 

Để giải thích mã này, nó sử dụng ConcurrentMap, cho phép sử dụng putIfAbsent. Bạn có thể làm điều này:

locks.putIfAbsent(id, new Object()); 

nhưng sau đó bạn phải chịu chi phí (nhỏ) để tạo đối tượng cho mỗi lần truy cập. Để tránh điều đó, tôi chỉ lưu số nguyên trong chính số Map. Điều này đạt được điều gì? Tại sao điều này khác biệt với việc chỉ sử dụng Integer?

Khi bạn thực hiện get() từ Map, các phím được so sánh với equals() (hoặc ít nhất phương pháp được sử dụng tương đương với việc sử dụng equals()). Hai phiên bản Integer khác nhau có cùng giá trị sẽ bằng nhau. Vì vậy, bạn có thể vượt qua bất kỳ số lượng các phiên bản Integer khác nhau của "new Integer(5)" làm thông số cho getCacheSyncObject và bạn sẽ luôn chỉ lấy lại trường hợp đầu tiên được truyền trong giá trị đó.

Có nhiều lý do tại sao bạn không muốn đồng bộ hóa trên Integer ... bạn có thể gặp lỗi khi nhiều chủ đề đang đồng bộ hóa trên Integer đối tượng và do đó vô tình sử dụng cùng một khóa khi họ muốn sử dụng các khóa khác nhau. Bạn có thể khắc phục rủi ro này bằng cách sử dụng

locks.putIfAbsent(id, new Object()); 

phiên bản và do đó phát sinh (rất) chi phí nhỏ cho mỗi lần truy cập vào bộ nhớ cache. Làm điều này, bạn đảm bảo rằng lớp này sẽ thực hiện đồng bộ hóa của nó trên một đối tượng mà không có lớp nào khác sẽ được đồng bộ hóa. Luôn luôn là một điều tốt.

+0

Một người triển khai sẽ phải mở rộng giải pháp này để đồng bộ hóa bản đồ ổ khóa và thêm một số loại tham chiếu hoặc cơ chế giải phóng. – McDowell

+0

Có, nếu một mục được giải phóng khỏi bộ nhớ cache của trang, thì bạn muốn giải phóng khóa đã được sử dụng cho mục bộ nhớ cache của trang đó. – Eddie

+0

Có cuộc đua bên trong getCacheSyncObject không? Có đồng bộ có hiệu lực trên kết quả của cuộc gọi hoặc một số đối tượng khác để tính toán giá trị của biểu thức? Có một mở ở đó nó có thể kết thúc đồng bộ hóa trên 2 đối tượng khác nhau. PutIfAbsent từ một giải pháp khác có thể sẽ hoạt động. – johnny

3

Integer.valueOf() chỉ trả về các phiên bản được lưu trong bộ nhớ cache cho một phạm vi giới hạn. Bạn chưa chỉ định phạm vi của mình, nhưng nói chung, điều này sẽ không hoạt động.

Tuy nhiên, tôi thực sự khuyên bạn không nên thực hiện phương pháp này, ngay cả khi giá trị của bạn nằm trong phạm vi chính xác. Vì các trường hợp Integer được lưu trong bộ nhớ cache này có sẵn cho bất kỳ mã nào, bạn không thể kiểm soát hoàn toàn việc đồng bộ hóa, điều này có thể dẫn đến bế tắc. Đây là vấn đề tương tự mà mọi người đã cố gắng khóa vào kết quả của String.intern().

Khóa tốt nhất là biến riêng tư. Vì chỉ có mã của bạn có thể tham chiếu nó, bạn có thể đảm bảo rằng không có deadlocks nào xảy ra.

Nhân tiện, sử dụng WeakHashMap cũng không hoạt động. Nếu trường hợp phục vụ như là chìa khóa là không được chấp nhận, nó sẽ được thu gom rác. Và nếu nó được tham chiếu mạnh mẽ, bạn có thể sử dụng nó trực tiếp.

1

Làm thế nào về một ConcurrentHashMap với các đối tượng Integer làm khóa?

+0

Vâng, như tôi đã đề cập, tôi có thể sử dụng một số loại bản đồ , nơi các đối tượng sẽ là khóa riêng. Tôi đã quan tâm nhiều hơn trong việc tìm ra nếu có một số cách trong java đảm bảo một ví dụ Integer cụ thể. –

1

Bạn có thể xem this code để tạo mutex từ ID. Mã này được viết cho các ID chuỗi, nhưng có thể dễ dàng được chỉnh sửa cho các đối tượng Integer.

3

Sử dụng đồng bộ hóa trên một số nguyên âm thanh thực sự sai theo thiết kế.

Nếu bạn cần đồng bộ hóa từng mục riêng lẻ trong khi truy xuất/lưu trữ, bạn có thể tạo Tập hợp và lưu trữ ở đó các mục hiện bị khóa. Nói cách khác,

// this contains only those IDs that are currently locked, that is, this 
// will contain only very few IDs most of the time 
Set<Integer> activeIds = ... 

Object retrieve(Integer id) { 
    // acquire "lock" on item #id 
    synchronized(activeIds) { 
     while(activeIds.contains(id)) { 
      try { 
       activeIds.wait(); 
      } catch(InterruptedExcption e){...} 
     } 
     activeIds.add(id); 
    } 
    try { 

     // do the retrieve here... 

     return value; 

    } finally { 
     // release lock on item #id 
     synchronized(activeIds) { 
      activeIds.remove(id); 
      activeIds.notifyAll(); 
     } 
    } 
} 

Cũng vậy với cửa hàng.

Điểm mấu chốt là: không có dòng mã nào giải quyết vấn đề này chính xác theo cách bạn cần.

+0

+1 Tôi về cơ bản giống như cách tiếp cận này. Chỉ có vấn đề tiềm năng mà tôi đề cập trong bài viết của tôi dưới đây mà dưới ganh đua cao, chờ đợi chủ đề cho tất cả ID được đánh thức khi bất kỳ một ID trở nên có sẵn. –

+0

Đây có thể không phải là vấn đề thực nếu bạn không có quá nhiều chủ đề. Nhưng nếu đó là trường hợp bạn có thể sử dụng Object.notify() và sử dụng Object.wait (maxTime) chỉ để đảm bảo không có người gọi bị mắc kẹt bao giờ hết. – Antonio

+0

Đồng ý có thể không phải là quá nhiều của một vấn đề dưới ganh đua thấp. Thật không may, tôi nghĩ rằng giải pháp bạn đề cập thực sự có thể tồi tệ hơn ...! –

4

Sử dụng bản đồ chỉ an toàn, chẳng hạn như ConcurrentHashMap. Điều này sẽ cho phép bạn thao tác bản đồ một cách an toàn, nhưng sử dụng một khóa khác để thực hiện tính toán thực.Bằng cách này, bạn có thể có nhiều tính toán chạy đồng thời với một bản đồ duy nhất.

Sử dụng ConcurrentMap.putIfAbsent, nhưng thay vì đặt giá trị thực tế, hãy sử dụng một Future với cấu trúc ánh sáng tính toán thay thế. Có thể triển khai FutureTask. Chạy tính toán và sau đó get kết quả, sẽ chặn an toàn luồng cho đến khi hoàn tất.

1

Như bạn có thể nhìn thấy từ sự đa dạng của câu trả lời, có những cách khác nhau để da mèo này:

  • Goetz et al của cách tiếp cận của việc giữ một bộ nhớ cache của FutureTasks hoạt động khá tốt trong những tình huống như đây là nơi bạn đang "lưu vào bộ nhớ gì đó" vì vậy đừng ngại xây dựng bản đồ các đối tượng FutureTask (và nếu bạn quan tâm đến bản đồ đang phát triển, ít nhất cũng dễ dàng cắt tỉa bản đồ đồng thời)
  • Như một câu trả lời chung cho "cách khóa ID", cách tiếp cận được Antonio nêu ra có lợi thế mũ đó là rõ ràng khi bản đồ của ổ khóa được thêm vào/gỡ bỏ từ.

Bạn có thể cần phải xem ra cho một vấn đề tiềm năng với Antonio thực hiện, cụ thể là các notifyAll() sẽ thức dậy đề chờ đợi vào tất cả ID khi một trong số họ trở nên có sẵn, mà có thể không quy mô rất tốt dưới ganh đua cao. Về nguyên tắc, tôi nghĩ rằng bạn có thể khắc phục điều đó bằng cách có một đối tượng Điều kiện cho mỗi ID hiện đang bị khóa, đó là điều bạn chờ đợi/tín hiệu. Tất nhiên, nếu trong thực tế, hiếm khi có nhiều hơn một ID được chờ đợi tại bất kỳ thời điểm nào, thì đây không phải là vấn đề.

1

Steve,

mã được đề xuất của bạn có một loạt vấn đề với đồng bộ hóa. (Antonio cũng vậy).

Để tóm tắt:

  1. Bạn cần bộ nhớ cache một đối tượng đắt .
  2. Bạn cần đảm bảo rằng trong khi một luồng đang thực hiện truy xuất, một chuỗi khác cũng không cố truy xuất cùng một đối tượng.
  3. Điều đó cho tất cả các chủ đề n cố gắng lấy đối tượng chỉ có 1 đối tượng được truy xuất và trả về.
  4. Điều đó cho các chủ đề yêu cầu các đối tượng khác nhau mà chúng không cạnh tranh với nhau.

mã giả để làm cho điều này xảy ra (sử dụng một ConcurrentHashMap như bộ nhớ cache):

ConcurrentMap<Integer, java.util.concurrent.Future<Page>> cache = new ConcurrentHashMap<Integer, java.util.concurrent.Future<Page>>; 

public Page getPage(Integer id) { 
    Future<Page> myFuture = new Future<Page>(); 
    cache.putIfAbsent(id, myFuture); 
    Future<Page> actualFuture = cache.get(id); 
    if (actualFuture == myFuture) { 
     // I am the first w00t! 
     Page page = getFromDataBase(id); 
     myFuture.set(page); 
    } 
    return actualFuture.get(); 
} 

Lưu ý:

  1. java.util.concurrent.Future là một giao diện
  2. java .util.concurrent.Tương lai không thực sự có một tập hợp() nhưng nhìn vào các lớp hiện có triển khai Tương lai để hiểu cách triển khai Tương lai của riêng bạn (Hoặc sử dụng FutureTask)
  3. Đẩy việc truy xuất thực tế vào một chuỗi công nhân gần như chắc chắn sẽ là một ý tưởng hay.
Các vấn đề liên quan