[英]Java Concurrency
我正在尝试在多线程环境中实现某种累加逻辑。 我想知道没有锁和同步关键字,有没有更好/更快的方法呢? 以下是我当前的代码:
public class ConcurrentHashMapTest {
private static final int NB_THREADS = 1_000;
private final Map<String, Integer> cnts = new HashMap<>();
private static final Lock RWLOCK = new ReentrantLock(true);
private static final String[] NAMES = {
"A", "B"
};
public void testIt() {
ExecutorService executor =
Executors.newFixedThreadPool(NB_THREADS);
for (int i = 0; i < NB_THREADS; i++) {
Runnable task = new WorkerThread();
executor.submit(task);
}
executor.shutdown();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(cnts);
}
private void accumulate(String name) {
RWLOCK.lock();
try {
Integer cnt = cnts.get(name);
if (cnt == null) {
cnts.put(name, 1);
} else {
cnts.put(name, cnt + 1);
}
} finally {
RWLOCK.unlock();
}
}
private class WorkerThread implements Runnable {
@Override
public void run() {
accumulate(NAMES[ThreadLocalRandom.current().nextInt(0, NAMES.length)]);
}
}
}
Java 8:
private final Map<String, AtomicInteger> cnts =
new ConcurrentHashMap<>();
private void accumulate(String name) {
cnts.computeIfAbsent(name, k -> new AtomicInteger()).incrementAndGet();
}
可以从多个线程自由访问ConcurrentHashMap
。 如果密钥不存在于映射中,则computeIfAbsent
方法采用一个lambda值来评估以获取密钥的值,并仅在不存在此类映射的情况下将其添加,然后返回该值。 实际上是putIfAbsent
然后是get
。 该值是值为0的新AtomicInteger
。是否存在现有值,或者是否刚刚添加了值为0的新值,无论哪种情况都将其递增。
Java 7:
private final ConcurrentMap<String, AtomicInteger> cnts =
new ConcurrentHashMap<>();
private void accumulate(String name) {
cnts.putIfAbsent(name, new AtomicInteger());
cnts.get(name).incrementAndGet();
}
对于Java 7,没有computeIfAbsent
方法,但是实际上只在putIfAbsent
之后执行get
,所以调用这些方法可以达到相同的效果。 不必担心地图中已经存在该值; 仅当映射图没有该键的值时,才会添加一个新的零AtomicInteger
。 即使另一个线程在我们之前进入那里并添加了零,两个线程也将看到并递增该相同的AtomicInteger
实例。
对String和AtomicInteger使用并发哈希映射。 两者都是线程安全的,因此可以自由使用。
在这种情况下,我会警惕在您的ReentrantLock上使用公平性,因为如果更长的等待线程首先获得访问权,则对您的累加器没有好处。 看看Brian Goetz的“ Java并发实践”
为什么我们不希望使所有锁公平? 毕竟公平是好事,不公平是坏事,对吧? (每当孩子们想提出一项决定时,几乎肯定会出现“那不公平”的想法并不是偶然的。我们认为公平非常重要,他们也知道这一点。)实际上,锁的公平保证是非常强大的,并且付出了巨大的性能成本。 确保公平所需的簿记和同步意味着竞争的公平锁将比不公平锁具有更低的吞吐量。 默认情况下,您应该将fair设置为false,除非对算法的正确性至关重要的是,线程必须按照它们排队的顺序进行服务。
您可以使用名称Map
到AtomicInteger
并在映射中根本没有计数器时使用双重检查锁定。 请注意,您需要使用volatile
关键字进行有效的双重检查锁定。
这样,您将只锁定整个地图以实际添加全新的条目,其余处理可以并行进行。
但是,您冒着使程序过于复杂的风险,甚至有可能在实际情况下降低性能。 这张地图上的竞争是否真的是性能瓶颈?
我认为您正在寻找的是Multiton
:
/**
* Holds a thread-safe map of unique create-once items.
*
* Contract:
*
* Only one object will be made for each key presented.
*
* Thread safe.
*
* @author OldCurmudgeon
* @param <K>
* @param <V>
*/
public class Multiton<K, V> {
// Map from the key to the futures of the items.
private final ConcurrentMap<K, Future<V>> multitons = new ConcurrentHashMap<>();
// The creator can create an item of type V.
private final Creator<K, V> creator;
public Multiton(Creator<K, V> creator) {
this.creator = creator;
}
/**
* There can be only one.
*
* Use a FutureTask to do the creation to ensure only one construction.
*
* @param key
* @return
* @throws InterruptedException
* @throws ExecutionException
*/
public V get(final K key) throws InterruptedException, ExecutionException {
// Already made?
Future<V> f = multitons.get(key);
if (f == null) {
// Plan the future but do not create as yet.
FutureTask<V> ft = new FutureTask<>(() -> creator.create(key));
// Store it.
f = multitons.putIfAbsent(key, ft);
if (f == null) {
// It was successfully stored - it is the first (and only)
f = ft;
// Make it happen.
ft.run();
}
}
// Wait for it to finish construction and return the constructed.
return f.get();
}
/**
* Returns a Map indicating the current state.
*
* @return a Map which should reflect the current state.
*
* @throws java.lang.InterruptedException
* @throws java.util.concurrent.ExecutionException
*/
public Map<K, V> getMap() throws InterruptedException, ExecutionException {
Map<K, V> map = new HashMap<>();
for (Map.Entry<K, Future<V>> e : multitons.entrySet()) {
map.put(e.getKey(), e.getValue().get());
}
return map;
}
/**
* User provides one of these to do the construction.
*
* @param <K>
* @param <V>
*/
public abstract static class Creator<K, V> {
// Return a new item under the key.
abstract V create(K key) throws ExecutionException;
}
}
用法-演示-累加所有最大为999的整数,并键入第一位数字:
Multiton<String, AtomicInteger> counts = new Multiton<>(
new Creator<String, AtomicInteger>() {
@Override
AtomicInteger create(String key) throws ExecutionException {
return new AtomicInteger();
}
}
);
public void test() throws InterruptedException, ExecutionException {
for (int i = 0; i < 1000; i++) {
counts.get(Integer.toString(i).substring(0, 1)).addAndGet(i);
}
System.out.println(counts.getMap());
}
印刷品:
{0=0, 1=15096, 2=25197, 3=35298, 4=45399, 5=55500, 6=65601, 7=75702, 8=85803, 9=95904}
Java <8版本:
/**
* Holds a thread-safe map of unique create-once items.
*
* Contract:
*
* Only one object will be made for each key presented.
*
* Thread safe.
*
* @author OldCurmudgeon
* @param <K>
* @param <V>
*/
public class Multiton<K, V> {
// Map from the key to the futures of the items.
private final ConcurrentMap<K, Future<V>> multitons = new ConcurrentHashMap<>();
// The creator can create an item of type V.
private final Creator<K, V> creator;
public Multiton(Creator<K, V> creator) {
this.creator = creator;
}
/**
* There can be only one.
*
* Use a FutureTask to do the creation to ensure only one construction.
*
* @param key
* @return
* @throws InterruptedException
* @throws ExecutionException
*/
public V get(final K key) throws InterruptedException, ExecutionException {
// Already made?
Future<V> f = multitons.get(key);
if (f == null) {
// Plan the future but do not create as yet.
FutureTask<V> ft = new FutureTask<>(new Callable<V>() {
@Override
public V call() throws Exception {
// Doing this inline may be a little contrived but it maintains the linkage with the Java-8 version.
return creator.create(key);
}
}
);
// Store it.
f = multitons.putIfAbsent(key, ft);
if (f == null) {
// It was successfully stored - it is the first (and only)
f = ft;
// Make it happen.
ft.run();
}
}
// Wait for it to finish construction and return the constructed.
return f.get();
}
/**
* Returns a Map indicating the current state.
*
* @return a Map which should reflect the current state.
*
* @throws java.lang.InterruptedException
* @throws java.util.concurrent.ExecutionException
*/
public Map<K, V> getMap() throws InterruptedException, ExecutionException {
Map<K, V> map = new HashMap<>();
for (Map.Entry<K, Future<V>> e : multitons.entrySet()) {
map.put(e.getKey(), e.getValue().get());
}
return map;
}
/**
* User provides one of these to do the construction.
*
* @param <K>
* @param <V>
*/
public abstract static class Creator<K, V> {
// Return a new item under the key.
abstract V create(K key) throws ExecutionException;
}
}
根据Oracle Java 7 API :HashMap的实现不同步。
您可以使用Hashtable
实现或声明: private final Map<String, Integer> cnts = Collections.synchronizedMap(new HashMap<String, Integer>());
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.