2010-07-26 24 views
28

Tôi muốn thu thập một số chỉ số từ các vị trí khác nhau trong ứng dụng web. Để đơn giản, tất cả những điều này sẽ là bộ đếm và do đó hoạt động sửa đổi duy nhất là tăng chúng lên 1.Bộ đếm gia tăng nguyên tử được lưu trữ trong ConcurrentHashMap

Gia số sẽ đồng thời và thường xuyên. Các lần đọc (bán phá giá các số liệu thống kê) là một hoạt động hiếm hoi.

Tôi đã nghĩ đến việc sử dụng ConcurrentHashMap. Vấn đề là làm thế nào để tăng các bộ đếm chính xác. Vì bản đồ không có thao tác "tăng dần", tôi cần phải đọc giá trị hiện tại trước tiên, tăng giá trị đó hơn đặt giá trị mới trong bản đồ. Nếu không có nhiều mã, đây không phải là một hoạt động nguyên tử.

Có thể đạt được điều này mà không cần đồng bộ hóa (điều này sẽ đánh bại mục đích của ConcurrentHashMap)? Tôi có cần phải xem Guava không?

Cảm ơn mọi con trỏ.


P.S.
Có một câu hỏi liên quan về SO (Most efficient way to increment a Map value in Java) nhưng tập trung vào hiệu suất và không đa luồng

CẬP NHẬT
Đối với những người đến đây thông qua tìm kiếm trên cùng một chủ đề: bên cạnh những câu trả lời dưới đây, có một hữu ích presentation ngẫu nhiên bao gồm cùng một chủ đề. Xem trang trình bày 24-33.

Trả lời

7

Bạn đang ở khá gần. Tại sao bạn không thử một cái gì đó như một ConcurrentHashMap<Key, AtomicLong>? Nếu số liệu Key của bạn không thay đổi, bạn thậm chí có thể chỉ sử dụng tiêu chuẩn HashMap (chúng an toàn nếu chỉ đọc, nhưng bạn cũng nên làm rõ điều này với ImmutableMap từ Google Bộ sưu tập hoặc Collections.unmodifiableMap, v.v.).

Bằng cách này, bạn có thể sử dụng map.get(myKey).incrementAndGet() để tăng số liệu thống kê.

+1

Chỉ cần đừng quên để lưu trữ các 'HashMap' trong một Thành viên 'final'. Và tốt hơn nên bọc bản đồ vào trong một trình bao bọc 'unmodifiable'. Tốt hơn, bạn có thể sử dụng 'ImmutableMap' từ Guava (siêu bộ sưu tập của google) và nó thực sự rất nhanh. –

+0

@Zwei: điểm tốt, chỉnh sửa câu trả lời để bao gồm lời khuyên đó :) –

+0

Danh sách các chỉ số được xây dựng khi chúng cung cấp dữ liệu (tức là các phím của bản đồ sẽ được thêm vào khi hệ thống chạy và các điểm thu thập khác nhau được nhấn; ưu tiên sẽ dễ xảy ra lỗi). Tôi quên về incrementAndGet của AtomicLong(), đó chỉ là những gì tôi cần. Nếu nó không tồn tại, tôi đã nghĩ rằng một cách tiếp cận khác sẽ là dành cho những người thu thập số liệu không phải để tăng các quầy, nhưng chỉ đơn giản là thêm yêu cầu để làm như vậy trong một hàng đợi được duy trì bởi singleton. Do đó, người gọi chỉ thêm() vào danh sách, định kỳ được đọc và xử lý. Không đơn giản như vậy. – wishihadabettername

4

khác vì đi với AtomicLong, bạn có thể làm cas-loop thông thường điều:

private final ConcurrentMap<Key,Long> counts = 
    new ConcurrentHashMap<Key,Long>(); 

public void increment(Key key) { 
    if (counts.putIfAbsent(key, 1)) == null) { 
     return; 
    } 

    Long old; 
    do { 
     old = counts.get(key); 
    } while (!counts.replace(key, old, old+1)); // Assumes no removal. 
} 

(Tôi đã không viết một do - loop while cho các lứa tuổi.)

Đối với giá trị nhỏ Long có thể sẽ được "lưu vào bộ nhớ cache". Đối với các giá trị dài hơn, nó có thể yêu cầu phân bổ. Nhưng phân bổ thực sự rất nhanh (và bạn có thể nhớ thêm) - phụ thuộc vào những gì bạn mong đợi, trong trường hợp xấu nhất.

+0

Tôi nghĩ điều đó đúng. Xin lỗi cho bất cứ ai nhìn vào mã bị hỏng trước đó. (Bạn có thể sắp xếp lại nó để nó bắt đầu với một 'get', so sánh với null và chỉ sau đó thử' putIfAbsent', tiếp tục với một vòng lặp 'while' bình thường.) –

14

Số mới AtomicLongMap của Guava (trong bản phát hành 11) có thể giải quyết nhu cầu này.

+0

Đây là một câu trả lời hoàn hảo! So sánh xác suất sai lầm tương tranh giữa Guava vs được liệt kê ở đây mẫu mã tùy chỉnh. – snowindy

0

Bạn cần phải làm như vậy. Tôi đang sử dụng ConcurrentHashMap + AtomicInteger. Ngoài ra, ReentrantRW Lock đã được giới thiệu cho tuôn ra nguyên tử (hành vi rất giống nhau).

Được thử nghiệm với 10 Phím và 10 Chủ đề cho mỗi Khóa. Không có gì bị mất. Tôi chưa thử một số chủ đề xả, nhưng hy vọng nó sẽ hoạt động.

Bù trừ đơn cực lớn đang tra tấn tôi ... Tôi muốn loại bỏ RWLock và chia nhỏ xả nước thành từng miếng nhỏ. Ngày mai.

private ConcurrentHashMap<String,AtomicInteger> counters = new ConcurrentHashMap<String, AtomicInteger>(); 
private ReadWriteLock rwLock = new ReentrantReadWriteLock(); 

public void count(String invoker) { 

    rwLock.readLock().lock(); 

    try{ 
     AtomicInteger currentValue = counters.get(invoker); 
     // if entry is absent - initialize it. If other thread has added value before - we will yield and not replace existing value 
     if(currentValue == null){ 
      // value we want to init with 
      AtomicInteger newValue = new AtomicInteger(0); 
      // try to put and get old 
      AtomicInteger oldValue = counters.putIfAbsent(invoker, newValue); 
      // if old value not null - our insertion failed, lets use old value as it's in the map 
      // if old value is null - our value was inserted - lets use it 
      currentValue = oldValue != null ? oldValue : newValue; 
     } 

     // counter +1 
     currentValue.incrementAndGet(); 
    }finally { 
     rwLock.readLock().unlock(); 
    } 

} 

/** 
* @return Map with counting results 
*/ 
public Map<String, Integer> getCount() { 
    // stop all updates (readlocks) 
    rwLock.writeLock().lock(); 
    try{ 
     HashMap<String, Integer> resultMap = new HashMap<String, Integer>(); 
     // read all Integers to a new map 
     for(Map.Entry<String,AtomicInteger> entry: counters.entrySet()){ 
      resultMap.put(entry.getKey(), entry.getValue().intValue()); 
     } 
     // reset ConcurrentMap 
     counters.clear(); 
     return resultMap; 

    }finally { 
     rwLock.writeLock().unlock(); 
    } 

} 
+0

Đối với mỗi lần getCount được gọi là nó sẽ ngăn chặn việc viết. Điều này sẽ đảm bảo tính nhất quán giá trị. Nhưng sẽ tác động đến hiệu suất. – Sudhakar

20

Trong Java 8:

ConcurrentHashMap<String, LongAdder> map = new ConcurrentHashMap<>(); 

map.computeIfAbsent("key", k -> new LongAdder()).increment(); 
+0

ở dạng ngắn với liên kết trên phương thức: 'LongAdder :: increment' – pacman

+0

@pacman Bạn có thể giải thích? – ZhekaKozlov

+0

Tôi có nghĩa là phương pháp java 8 trên liên kết: 'AtomicInteger atomicInt = new AtomicInteger (0); atomicInt :: incrementAndGet' – pacman

-2

Các mã sau đây làm việc cho tôi mà không cần đồng bộ hóa cho tần số đếm từ

protected void updateFreqCountForText(Map<String, Integer> wordFreq, String line) { 
      ConcurrentMap<String, Integer> wordFreqConc = (ConcurrentMap<String, Integer>) wordFreq; 
      Pattern pattern = Pattern.compile("[a-zA-Z]+"); 
      Matcher matcher = pattern.matcher(line); 
      while (matcher.find()) { 
       String word = matcher.group().toLowerCase(); 
       Integer oldCount; 

       oldCount = wordFreqConc.get(word); 
       if (oldCount == null) { 
        oldCount = wordFreqConc.putIfAbsent(word, 1); 
        if (oldCount == null) 
         continue; 
        else wordFreqConc.put(word, oldCount + 1); 
       } 
       else 
        do { 
         oldCount = wordFreqConc.get(word); 
        } while (!wordFreqConc.replace(word, oldCount, oldCount + 1)); 

      } 
     } 
+1

* Mã sau hoạt động cho tôi không đồng bộ hóa * là một chỉ báo hoàn toàn vô nghĩa về an toàn luồng. – shmosel

+0

Bị bỏ qua. Mã này có thể thất bại nếu 3 lần thử thêm một khóa xuất hiện liên tiếp. – Persixty

+0

thất bại ở đâu? trong khác hoặc nếu chi nhánh? – user1264304

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