1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.collect;
18
19 import com.google.common.base.Function;
20 import com.google.common.primitives.Ints;
21
22 import junit.framework.TestCase;
23
24 import java.util.List;
25 import java.util.Random;
26 import java.util.concurrent.Callable;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ConcurrentMap;
29 import java.util.concurrent.ConcurrentSkipListMap;
30 import java.util.concurrent.ExecutionException;
31 import java.util.concurrent.ExecutorService;
32 import java.util.concurrent.Executors;
33 import java.util.concurrent.Future;
34 import java.util.concurrent.atomic.AtomicInteger;
35
36
37
38
39
40
41
42
43
44
45 public class ConcurrentHashMultisetBasherTest extends TestCase {
46
47 public void testAddAndRemove_ConcurrentHashMap() throws Exception {
48 testAddAndRemove(new ConcurrentHashMap<String, AtomicInteger>());
49 }
50
51 public void testAddAndRemove_ConcurrentSkipListMap() throws Exception {
52 testAddAndRemove(new ConcurrentSkipListMap<String, AtomicInteger>());
53 }
54
55 public void testAddAndRemove_MapMakerMap() throws Exception {
56 MapMaker mapMaker = new MapMaker();
57
58 mapMaker.useCustomMap = true;
59 testAddAndRemove(mapMaker.<String, AtomicInteger>makeMap());
60 }
61
62 private void testAddAndRemove(ConcurrentMap<String, AtomicInteger> map)
63 throws ExecutionException, InterruptedException {
64
65 final ConcurrentHashMultiset<String> multiset = new ConcurrentHashMultiset<String>(map);
66 int nThreads = 20;
67 int tasksPerThread = 10;
68 int nTasks = nThreads * tasksPerThread;
69 ExecutorService pool = Executors.newFixedThreadPool(nThreads);
70 ImmutableList<String> keys = ImmutableList.of("a", "b", "c");
71 try {
72 List<Future<int[]>> futures = Lists.newArrayListWithExpectedSize(nTasks);
73 for (int i = 0; i < nTasks; i++) {
74 futures.add(pool.submit(new MutateTask(multiset, keys)));
75 }
76
77 int[] deltas = new int[3];
78 for (Future<int[]> future : futures) {
79 int[] taskDeltas = future.get();
80 for (int i = 0; i < deltas.length; i++) {
81 deltas[i] += taskDeltas[i];
82 }
83 }
84
85 List<Integer> actualCounts = Lists.transform(keys,
86 new Function<String, Integer>() {
87 @Override public Integer apply(String key) {
88 return multiset.count(key);
89 }
90 });
91 assertEquals("Counts not as expected", Ints.asList(deltas), actualCounts);
92 } finally {
93 pool.shutdownNow();
94 }
95
96
97 for (AtomicInteger value : map.values()) {
98 assertTrue("map should not contain a zero", value.get() != 0);
99 }
100 }
101
102 private static class MutateTask implements Callable<int[]> {
103 private final ConcurrentHashMultiset<String> multiset;
104 private final ImmutableList<String> keys;
105 private final Random random = new Random();
106
107 private MutateTask(ConcurrentHashMultiset<String> multiset, ImmutableList<String> keys) {
108 this.multiset = multiset;
109 this.keys = keys;
110 }
111
112 @Override public int[] call() throws Exception {
113 int iterations = 100000;
114 int nKeys = keys.size();
115 int[] deltas = new int[nKeys];
116 Operation[] operations = Operation.values();
117 for (int i = 0; i < iterations; i++) {
118 int keyIndex = random.nextInt(nKeys);
119 String key = keys.get(keyIndex);
120 Operation op = operations[random.nextInt(operations.length)];
121 switch (op) {
122 case ADD: {
123 int delta = random.nextInt(10);
124 multiset.add(key, delta);
125 deltas[keyIndex] += delta;
126 break;
127 }
128 case SET_COUNT: {
129 int newValue = random.nextInt(3);
130 int oldValue = multiset.setCount(key, newValue);
131 deltas[keyIndex] += (newValue - oldValue);
132 break;
133 }
134 case SET_COUNT_IF: {
135 int newValue = random.nextInt(3);
136 int oldValue = multiset.count(key);
137 if (multiset.setCount(key, oldValue, newValue)) {
138 deltas[keyIndex] += (newValue - oldValue);
139 }
140 break;
141 }
142 case REMOVE: {
143 int delta = random.nextInt(6);
144 int oldValue = multiset.remove(key, delta);
145 deltas[keyIndex] -= Math.min(delta, oldValue);
146 break;
147 }
148 case REMOVE_EXACTLY: {
149 int delta = random.nextInt(5);
150 if (multiset.removeExactly(key, delta)) {
151 deltas[keyIndex] -= delta;
152 }
153 break;
154 }
155 }
156 }
157 return deltas;
158 }
159
160 private enum Operation {
161 ADD,
162 SET_COUNT,
163 SET_COUNT_IF,
164 REMOVE,
165 REMOVE_EXACTLY,
166 ;
167 }
168 }
169 }