Synchronized HashMap 与 ConcurrentHashMap 写测试
Synchronized HashMap vs ConcurrentHashMap write test
我学习java.util.concurrency并且找到了一篇关于性能的文章(http://www.javamex.com/tutorials/concurrenthashmap_scalability.shtml)。我决定为了学习目的重复这些性能测试的一小部分。我已经为 HashMap
和 ConcurrentHashMap
编写了写测试。
我有两个问题:
- 为了获得最佳性能,我应该使用数量等于 CPU 核心的线程数吗?
- 我知道,性能因平台而异。但结果显示
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 个线程可能只会导致严重的抖动。但是,如果您有大量磁盘 activity,接着是繁重的计算,然后是更多磁盘 activity,您将受益于错开线程、拆分活动并将它们分组,以及使用更多线程。例如,在这种情况下,您可能希望将使用单个文件的文件 activity 分组在一起,但可能不希望 activity 从一堆文件中提取文件(除非它们被写入连续在磁盘上)。当然,如果您过度考虑磁盘 IO,您可能会严重损害您的性能,但我要强调的是您也不应该只是逃避它。在这样的程序中,我可能会有专用于磁盘 IO 的线程,专用于 CPU 工作的线程。分而治之。您将拥有更少的 IO 线程和更多 CPU 个线程。
同步服务器 运行 比 cores/CPUs 多得多的线程是很常见的,因为大多数线程要么只工作很短的时间,要么不做太多事情 CPU 密集工作。但是,如果您只有 2 个客户端并且那些多余线程的上下文切换会影响性能,那么拥有 500 个线程是没有用的。这是一种平衡行为,通常需要进行一些调整。
简而言之
- 想想你在做什么在做什么
- 网络activity轻,多线程一般好
- CPU 如果这些线程的数量是内核的 2 倍,那么密集的事情不会有太大好处......通常多于 1 倍或少于 1 倍是最佳的,但你必须测试,测试,测试
- 拥有 10 个磁盘 IO 密集型线程可能会伤害所有 10 个线程,就像拥有 30 个 CPU 密集型线程一样……颠簸会伤害他们所有人
- 试着分散痛苦
- 看看它是否有助于分散 CPU、IO 等工作,或者集群是否更好……这将取决于您在做什么
- 试着把事情分组
- 如果可以,将您的磁盘、IO 和网络任务分开,并为它们提供针对这些任务调整的自己的线程
两个
一般来说,线程不安全的方法 运行 更快。同样,使用局部同步比同步整个方法快 运行s。因此,HashMap 通常比 ConcurrentHashMap 快得多。另一个例子是 StringBuffer 与 StringBuilder 的比较。 StringBuffer 是同步的,不仅速度慢,而且同步更重(更多代码等);它应该很少被使用。但是,如果您有多个线程访问它,StringBuilder 是 不安全的 。话虽如此,StringBuffer 和 ConcurrentHashMap 也可以竞争。 "thread-safe" 并不意味着你可以不假思索地使用它,尤其是这两个 class 的运作方式。例如,如果您同时读取和写入(例如,在执行 put 或 remove 时使用 contains(Object) ),您仍然可能有竞争条件。如果你想防止这样的事情发生,你必须使用你自己的 class 或将你的调用同步到你的 ConcurrentHashMap。
我通常使用非并发映射和集合,只在我需要的地方使用我自己的锁。你会发现那样快多了,控制也很好。原子(例如 AtomicInteger)有时很好,但实际上对我所做的事情通常没有用。使用 classes,使用同步,你会发现你可以比 ConcurrentHashMap、StringBuffer 等散弹枪方法更有效地掌握。无论你是否使用这些 class是的,如果你做的不对......但如果你自己做,你也可以更有效率和更小心。
例子
请注意,我们有一个正在锁定的新对象。在方法上使用它而不是 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,而是每次都添加 nIteration 更多项目...这是您想要的吗?
synchronized
在您的 getNextInt/getNextString 上不是必需的,因为它们不是从多个线程调用的。
我学习java.util.concurrency并且找到了一篇关于性能的文章(http://www.javamex.com/tutorials/concurrenthashmap_scalability.shtml)。我决定为了学习目的重复这些性能测试的一小部分。我已经为 HashMap
和 ConcurrentHashMap
编写了写测试。
我有两个问题:
- 为了获得最佳性能,我应该使用数量等于 CPU 核心的线程数吗?
- 我知道,性能因平台而异。但结果显示
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 个线程可能只会导致严重的抖动。但是,如果您有大量磁盘 activity,接着是繁重的计算,然后是更多磁盘 activity,您将受益于错开线程、拆分活动并将它们分组,以及使用更多线程。例如,在这种情况下,您可能希望将使用单个文件的文件 activity 分组在一起,但可能不希望 activity 从一堆文件中提取文件(除非它们被写入连续在磁盘上)。当然,如果您过度考虑磁盘 IO,您可能会严重损害您的性能,但我要强调的是您也不应该只是逃避它。在这样的程序中,我可能会有专用于磁盘 IO 的线程,专用于 CPU 工作的线程。分而治之。您将拥有更少的 IO 线程和更多 CPU 个线程。
同步服务器 运行 比 cores/CPUs 多得多的线程是很常见的,因为大多数线程要么只工作很短的时间,要么不做太多事情 CPU 密集工作。但是,如果您只有 2 个客户端并且那些多余线程的上下文切换会影响性能,那么拥有 500 个线程是没有用的。这是一种平衡行为,通常需要进行一些调整。
简而言之
- 想想你在做什么在做什么
- 网络activity轻,多线程一般好
- CPU 如果这些线程的数量是内核的 2 倍,那么密集的事情不会有太大好处......通常多于 1 倍或少于 1 倍是最佳的,但你必须测试,测试,测试
- 拥有 10 个磁盘 IO 密集型线程可能会伤害所有 10 个线程,就像拥有 30 个 CPU 密集型线程一样……颠簸会伤害他们所有人
- 试着分散痛苦
- 看看它是否有助于分散 CPU、IO 等工作,或者集群是否更好……这将取决于您在做什么
- 试着把事情分组
- 如果可以,将您的磁盘、IO 和网络任务分开,并为它们提供针对这些任务调整的自己的线程
两个
一般来说,线程不安全的方法 运行 更快。同样,使用局部同步比同步整个方法快 运行s。因此,HashMap 通常比 ConcurrentHashMap 快得多。另一个例子是 StringBuffer 与 StringBuilder 的比较。 StringBuffer 是同步的,不仅速度慢,而且同步更重(更多代码等);它应该很少被使用。但是,如果您有多个线程访问它,StringBuilder 是 不安全的 。话虽如此,StringBuffer 和 ConcurrentHashMap 也可以竞争。 "thread-safe" 并不意味着你可以不假思索地使用它,尤其是这两个 class 的运作方式。例如,如果您同时读取和写入(例如,在执行 put 或 remove 时使用 contains(Object) ),您仍然可能有竞争条件。如果你想防止这样的事情发生,你必须使用你自己的 class 或将你的调用同步到你的 ConcurrentHashMap。
我通常使用非并发映射和集合,只在我需要的地方使用我自己的锁。你会发现那样快多了,控制也很好。原子(例如 AtomicInteger)有时很好,但实际上对我所做的事情通常没有用。使用 classes,使用同步,你会发现你可以比 ConcurrentHashMap、StringBuffer 等散弹枪方法更有效地掌握。无论你是否使用这些 class是的,如果你做的不对......但如果你自己做,你也可以更有效率和更小心。
例子
请注意,我们有一个正在锁定的新对象。在方法上使用它而不是 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,而是每次都添加 nIteration 更多项目...这是您想要的吗?
synchronized
在您的 getNextInt/getNextString 上不是必需的,因为它们不是从多个线程调用的。