[英]Synchronized HashMap vs ConcurrentHashMap write test
我學習java.util.concurrency,我發現了一篇關於性能的文章( http://www.javamex.com/tutorials/concurrenthashmap_scalability.shtml )。 我決定重復這些性能測試的一小部分用於研究目的。 我已經為HashMap
和ConcurrentHashMap
編寫了寫測試。 我有兩個問題:
HashMap
比ConcurrentHashMap
快一點。 我認為應該是相同的,反之亦然。 也許我在我的代碼中犯了一個錯誤。 任何批評都是受歡迎的。
package Concurrency;
import java.util.concurrent.*;
import java.util.*;
class Writer2 implements Runnable {
private Map<String, Integer> map;
private static int index;
private int nIteration;
private Random random = new Random();
char[] chars = "abcdefghijklmnopqrstuvwxyz".toCharArray();
private final CountDownLatch latch;
public Writer2(Map<String, Integer> map, int nIteration, CountDownLatch latch) {
this.map = map;
this.nIteration = nIteration;
this.latch = latch;
}
private synchronized String getNextString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
char c = chars[random.nextInt(chars.length)];
sb.append(c);
}
sb.append(index);
if(map.containsKey(sb.toString()))
System.out.println("dublicate:" + sb.toString());
return sb.toString();
}
private synchronized int getNextInt() { return index++; }
@Override
public void run() {
while(nIteration-- > 0) {
map.put(getNextString(), getNextInt());
}
latch.countDown();
}
}
public class FourtyTwo {
static final int nIteration = 100000;
static final int nThreads = 4;
static Long testMap(Map<String, Integer> map) throws InterruptedException{
String name = map.getClass().getSimpleName();
CountDownLatch latch = new CountDownLatch(nThreads);
long startTime = System.currentTimeMillis();
ExecutorService exec = Executors.newFixedThreadPool(nThreads);
for(int i = 0; i < nThreads; i++)
exec.submit(new Writer2(map, nIteration, latch));
latch.await();
exec.shutdown();
long endTime = System.currentTimeMillis();
System.out.format(name + ": that took %,d milliseconds %n", (endTime - startTime));
return (endTime - startTime);
}
public static void main(String[] args) throws InterruptedException {
ArrayList<Long> result = new ArrayList<Long>() {
@Override
public String toString() {
Long result = 0L;
Long size = new Long(this.size());
for(Long i : this)
result += i;
return String.valueOf(result/size);
}
};
Map<String, Integer> map1 = Collections.synchronizedMap(new HashMap<String, Integer>());
Map<String, Integer> map2 = new ConcurrentHashMap<>();
System.out.println("Rinning test...");
for(int i = 0; i < 5; i++) {
//result.add(testMap(map1));
result.add(testMap(map2));
}
System.out.println("Average time:" + result + " milliseconds");
}
}
/*
OUTPUT:
ConcurrentHashMap: that took 5 727 milliseconds
ConcurrentHashMap: that took 2 349 milliseconds
ConcurrentHashMap: that took 9 530 milliseconds
ConcurrentHashMap: that took 25 931 milliseconds
ConcurrentHashMap: that took 1 056 milliseconds
Average time:8918 milliseconds
SynchronizedMap: that took 6 471 milliseconds
SynchronizedMap: that took 2 444 milliseconds
SynchronizedMap: that took 9 678 milliseconds
SynchronizedMap: that took 10 270 milliseconds
SynchronizedMap: that took 7 206 milliseconds
Average time:7213 milliseconds
*/
有多少線程不同,不是CPU,而是你正在做的事情。 例如,如果您對線程執行的操作是高度磁盤密集型的,那么您的CPU可能不會被最大化,因此執行8個線程可能只會導致嚴重的顛簸。 但是,如果你有大量的磁盤活動,接着是繁重的計算,然后是更多的磁盤活動,你可以從錯開線程,拆分活動和分組,以及使用更多線程中受益。 例如,在這種情況下,您可能希望將使用單個文件的文件活動組合在一起,但可能不是您從一堆文件中提取的活動(除非它們是在磁盤上連續寫入的)。 當然,如果你過度思考磁盤IO,你可能會嚴重損害你的性能,但我要說的是你不應該只是推卸它。 在這樣的程序中,我可能會有專用於磁盤IO的線程,專用於CPU工作的線程。 分而治之。 您擁有更少的IO線程和更多的CPU線程。
同步服務器運行比核心/ CPU更多的線程是很常見的,因為大多數線程只能在很短的時間內工作,或者不會進行大量CPU密集型工作。 但是,擁有500個線程沒有用,如果你只有2個客戶端,那些多余線程的上下文切換會妨礙性能。 這是一種平衡行為,通常需要一點點調整。
簡而言之
通常,線程不安全的方法運行得更快。 類似地,使用本地化同步比同步整個方法運行得更快。 因此,HashMap通常比ConcurrentHashMap快得多。 與StringBuilder相比,另一個例子是StringBuffer。 StringBuffer是同步的,不僅速度慢,而且同步更重(更多代碼等); 它應該很少使用。 但是,如果有多個線程命中它,StringBuilder是不安全的 。 話雖如此,StringBuffer和ConcurrentHashMap也可以競爭。 “線程安全”並不意味着您可以毫無思考地使用它,特別是這兩個類的操作方式。 例如,如果您同時進行讀寫操作(例如,在執行put或remove時使用contains(Object)),您仍然可以處於競爭狀態。 如果要防止此類事情,則必須使用自己的類或將調用同步到ConcurrentHashMap。
我通常使用非並發映射和集合,只需在我需要的地方使用自己的鎖。 你會發現它的速度要快得多,控制也很棒。 Atomics(例如AtomicInteger)有時很好,但對我的工作通常並不常用。 玩類,玩同步,你會發現你可以比ConcurrentHashMap,StringBuffer等的霰彈槍方法更有效地掌握你。如果你不這樣做,你可以有競爭條件,無論你是否使用這些類它是對的...但如果你自己做,你也可以更高效,更小心。
請注意,我們有一個新的Object,我們正在鎖定它。 在方法上使用此而不是synchronized
。
public final class Fun {
private final Object lock = new Object();
/*
* (non-Javadoc)
*
* @see java.util.Map#clear()
*/
@Override
public void clear() {
// Doing things...
synchronized (this.lock) {
// Where we do sensitive work
}
}
/*
* (non-Javadoc)
*
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
@Override
public V put(final K key, @Nullable final V value) {
// Doing things...
synchronized (this.lock) {
// Where we do sensitive work
}
// Doing things...
}
}
我可能不會把sb.append(index)放在鎖中,或者可能有單獨的鎖用於索引調用,但是......
private final Object lock = new Object();
private String getNextString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
char c = chars[random.nextInt(chars.length)];
sb.append(c);
}
synchronized (lock) {
sb.append(index);
if (map.containsKey(sb.toString()))
System.out.println("dublicate:" + sb.toString());
}
return sb.toString();
}
private int getNextInt() {
synchronized (lock) {
return index++;
}
}
您鏈接的文章並未聲明ConcurrentHashMap通常比同步HashMap更快,只是它更好地擴展; 即對於大量線程來說它更快。 正如您在圖表中看到的,對於4個線程,性能非常相似。
除此之外,您應該測試更多項目,正如我的結果所示,它們可能會有很大不同:
Rinning test...
SynchronizedMap: that took 13,690 milliseconds
SynchronizedMap: that took 8,210 milliseconds
SynchronizedMap: that took 11,598 milliseconds
SynchronizedMap: that took 9,509 milliseconds
SynchronizedMap: that took 6,992 milliseconds
Average time:9999 milliseconds
Rinning test...
ConcurrentHashMap: that took 10,728 milliseconds
ConcurrentHashMap: that took 7,227 milliseconds
ConcurrentHashMap: that took 6,668 milliseconds
ConcurrentHashMap: that took 7,071 milliseconds
ConcurrentHashMap: that took 7,320 milliseconds
Average time:7802 milliseconds
請注意,您的代碼不會清除循環之間的Map,每次添加更多項目...是您想要的嗎?
您的getNextInt / getNextString不需要synchronized
,因為它們不是從多個線程調用的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.