2012-08-27 41 views
9

Tôi đang cố gắng tạo một giá trị Map với các giá trị int và tăng chúng theo nhiều luồng. hai hoặc nhiều chủ đề có thể tăng cùng một khóa.bản đồ java cập nhật đồng thời

ConcurrentHashMap tài liệu hướng dẫn rất rõ ràng đối với tôi vì nó sais rằng:

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove)

Tôi tự hỏi nếu đoạn mã sau sử dụng ConcurrentHashMap ý chí làm việc đúng:

myMap.put(X, myMap.get(X) + 1);

nếu không muốn nói , làm thế nào tôi có thể quản lý điều đó?

+0

Trong Java 8, điều này có thể an toàn được thực hiện với 'myMap.merge (X, 1, Integer: : sum) '. – shmosel

Trả lời

9

Bản đồ đồng thời sẽ không giúp an toàn chủ đề cho mã của bạn. Bạn vẫn có thể nhận được điều kiện chủng tộc:

Thread-1: x = 1, get(x) 
Thread-2: x = 1, get(x) 
Thread-1: put(x + 1) => 2 
Thread-2: put(x + 1) => 2 

Hai lần tăng xảy ra, nhưng bạn vẫn chỉ nhận được +1. Bạn chỉ cần bản đồ đồng thời nếu bạn muốn sửa đổi bản đồ, chứ không phải nội dung của bản đồ. Ngay cả đơn giản nhất HashMapthreadsafe for concurrent reads, do bản đồ không bị tắt nữa.

Vì vậy, thay vì bản đồ luồng an toàn cho loại nguyên thủy, bạn cần trình bao bọc luồng an toàn cho loại. Hoặc là một cái gì đó từ java.util.concurrent.atomic hoặc cuộn hộp chứa bị khóa của riêng bạn nếu cần một loại tùy ý.

1

Bạn chỉ có thể đặt hoạt động trong khối synchronized (myMap) {...}.

3

Một ý tưởng sẽ kết hợp ConcurrentMap với AtomicInteger, có một phương pháp gia tăng.

AtomicInteger current = map.putIfAbsent(key, new AtomicInteger(1)); 
int newValue = current == null ? 1 :current.incrementAndGet(); 

hoặc (hiệu quả hơn, nhờ @Keppil) với một bảo vệ mã thêm để tránh không cần thiết tạo đối tượng:

AtomicInteger current = map.get(key); 
if (current == null){ 
    current = map.putIfAbsent(key, new AtomicInteger(1)); 
} 
int newValue = current == null ? 1 : current.incrementAndGet(); 
+0

nếu bạn sử dụng một ConcurrentMap không có điểm trong cũng bằng cách sử dụng nguyên nguyên nguyên, ConcurrentHashMap.replace (K, V, V) được thực hiện cho việc này. – jolivier

+0

Nếu cập nhật hiếm, một đồng bộ đơn giản như @dflemstr gợi ý cũng sẽ hoạt động. Bạn không chắc mình cần bao nhiêu thông lượng cho AtomicInteger. – Thilo

+1

@jolivier 'replace' có thể được thử lại, trong khi' getAndIncrement' thì không. –

0

mã hiện tại của bạn thay đổi các giá trị của bản đồ của bạn đồng thời vì vậy đây sẽ không hoạt động.

Nếu nhiều chủ đề có thể put giá trị vào bản đồ của bạn, bạn phải sử dụng bản đồ đồng thời như ConcurrentHashMap với các giá trị an toàn không phải là chủ đề như Integer. ConcurrentMap.replace sau đó sẽ làm những gì bạn muốn (hoặc sử dụng AtomicInteger để giảm bớt mã của bạn).

Nếu chủ đề của bạn sẽ chỉ thay đổi các giá trị (và không thêm/thay đổi các phím) của bản đồ của bạn, sau đó bạn có thể sử dụng một tiêu chuẩn đồ lưu trữ chủ đề giá trị an toàn như AtomicInteger. Sau đó, chủ đề của bạn sẽ gọi: map.get(key).incrementAndGet() chẳng hạn.

2

Thực tiễn tốt nhất. Bạn có thể sử dụng HashMap và AtomicInteger.đang Test:

public class HashMapAtomicIntegerTest { 
    public static final int KEY = 10; 

    public static void main(String[] args) { 
     HashMap<Integer, AtomicInteger> concurrentHashMap = new HashMap<Integer, AtomicInteger>(); 
     concurrentHashMap.put(HashMapAtomicIntegerTest.KEY, new AtomicInteger()); 
     List<HashMapAtomicCountThread> threadList = new ArrayList<HashMapAtomicCountThread>(); 
     for (int i = 0; i < 500; i++) { 
      HashMapAtomicCountThread testThread = new HashMapAtomicCountThread(
        concurrentHashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       HashMapAtomicCountThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" 
       + concurrentHashMap.get(HashMapAtomicIntegerTest.KEY)); 
    } 
} 

class HashMapAtomicCountThread extends Thread { 
    HashMap<Integer, AtomicInteger> concurrentHashMap = null; 

    public HashMapAtomicCountThread(
      HashMap<Integer, AtomicInteger> concurrentHashMap) { 
     this.concurrentHashMap = concurrentHashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      concurrentHashMap.get(HashMapAtomicIntegerTest.KEY) 
        .getAndIncrement(); 
     } 
    } 
} 

Kết quả:

Giá trị kết quả nên được 5000000, thực sự is5000000

Hoặc HashMap và đồng bộ, nhưng chậm hơn nhiều so với trước đây

public class HashMapSynchronizeTest { 

    public static final int KEY = 10; 

    public static void main(String[] args) { 

     HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>(); 
     hashMap.put(KEY, 0); 
     List<HashMapSynchronizeThread> threadList = new ArrayList<HashMapSynchronizeThread>(); 
     for (int i = 0; i < 500; i++) { 
      HashMapSynchronizeThread testThread = new HashMapSynchronizeThread(
        hashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       HashMapSynchronizeThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" + hashMap.get(KEY)); 
    } 
} 

class HashMapSynchronizeThread extends Thread { 
    HashMap<Integer, Integer> hashMap = null; 

    public HashMapSynchronizeThread(
      HashMap<Integer, Integer> hashMap) { 
     this.hashMap = hashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      synchronized (hashMap) { 
       hashMap.put(HashMapSynchronizeTest.KEY, 
         hashMap 
           .get(HashMapSynchronizeTest.KEY) + 1); 
      } 
     } 
    } 
} 

Kết quả:

Giá trị kết quả nên được 5000000, thực sự is5000000

Sử dụng ConcurrentHashMap sẽ có được kết quả sai.

public class ConcurrentHashMapTest { 

    public static final int KEY = 10; 

    public static void main(String[] args) { 
     ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<Integer, Integer>(); 
     concurrentHashMap.put(KEY, 0); 
     List<CountThread> threadList = new ArrayList<CountThread>(); 
     for (int i = 0; i < 500; i++) { 
      CountThread testThread = new CountThread(concurrentHashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       CountThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" + concurrentHashMap.get(KEY)); 
    } 
} 

class CountThread extends Thread { 
    ConcurrentHashMap<Integer, Integer> concurrentHashMap = null; 

    public CountThread(ConcurrentHashMap<Integer, Integer> concurrentHashMap) { 
     this.concurrentHashMap = concurrentHashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      concurrentHashMap.put(ConcurrentHashMapTest.KEY, 
        concurrentHashMap.get(ConcurrentHashMapTest.KEY) + 1); 
     } 
    } 
} 

Kết quả:

Giá trị kết quả nên được 5000000, thực sự is11759

+0

Bạn có thể tìm hiểu nguyên tắc từ câu trả lời của @vtmarvin – wodong

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