Java并发增加值 [英] Java Concurrency Incrementing a Value

查看:260
本文介绍了Java并发增加值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在阅读关于在Java中的 volatile 同步,但一直困扰我的头。我希望有人可以帮助我清理一个问题

  private HashMap< String,int& map = new HashMap< String,int>(); 

在我的线程

  if(map.get(value)== null)
{
map.put(value,0);
}
map.put(value,map.get(value)+ 1);

我的目标是让所有的线程共享这个 map 。如果我添加 volatile 它似乎并没有解决我的问题(我输出,并看到 map 正在覆盖每次)。然后我尝试使用 ConcurrentHashMap 并添加 volatile 前面似乎也没有工作。根据我对 volatile 的了解,我的理解是当时,它应该锁定对 map c> map 正在写入,然后当映射完成写入锁被释放。



所以...然后我尝试添加 static

  private static ConcurrentHashMap< String,int> map = new ConcurrentHashMap< String,int>(); 

这似乎工作完美...但...我继续读使用 static 是不正确的方式,因为关于争用的一些东西(我不太明白)



提前

解决方案

易失性 Volatile 有助于解决可见性问题,但您面临另一个问题:原子性



哦, volatile 锁定绝对无关。它不会在读/写时获取锁,它不会释放任何东西。它所做的是: 发生在 之前,对易失性字段的写入操作将在所有其他线程读取相同的易失性字段后可见。没有涉及到锁定(他们是相似的,释放/获取锁的记忆效应是完全一样的)。



操作 get set 不是原子的,意味着其他事情可能发生在两者之间。



例如,一个线程将 get 的值,然后ANOTHER线程将 get 相同的值,都将增加值,那么第一个会设置新的值,然后第二个将做同样的。

最常见的解决方案是序列化访问(即 synchronize )到共享变量,或使用比较和设置(CAS)(因此您不需要进行同步)。



1。 同步



  private final Map< String,Integer> m = new ConcurrentHashMap< String,Integer>(); 
synchronized incrementValue(final String valueName){
m.put(valueName,m.get(valueName)+ 1);
}

请注意,如果使用此解决方案,那么每个地图的访问必须同步在同一锁上



2。 CAS



许多CAS算法已经在JVM中以非常有效的方式实现了(即,它们使用本地代码,并且JIT可以使用特定于处理器的指令,你不能以其他方式访问 - 例如检查Sun的JVM中的类 Unsafe )。



可能对你有用的是 AtomicInteger 。你可以这样使用:

  private final Map< String,AtomicInteger> m = new ConcurrentHashMap< String,AtomicInteger>(); 
incrementValue(final String valueName){
m.get(valueName).incrementAndGet();
}

CAS算法会做什么是这样的:

  for(;;){
state = object.getCurrentState();
if(object.updateValueAndStateIfStateDidntChange(state)){
break;
}
}

假定方法 updateValueAndStateIfStateDidntChange 是原子的,只有当它能够更新该值时才会返回true。这样,如果另一个线程在获取状态之后并且在更新值之前修改了该值,那么该方法将返回false,并且循环将再次尝试。



假设你可以以不使用 synchronized 的方式实现该方法(并且可以通过使用java.util.concurrent中的类),避免争用(这意味着线程等待获得另一个线程持有的锁),你可能会看到性能的一般性改进。



我在分布式任务执行中使用了很多这样的东西系统我写。任务必须正好执行一次,我有很多机器执行任务。任务都在单个MySQL表中指定。怎么做?您必须有一个列的目的是允许CAS的实现。调用执行。在启动任务之前,必须执行以下操作:检索下一个任务更新任务设置执行= 1其中id =:id AND executing = 0计数更新的行数。如果你更新了0行,那是因为另一个线程/进程/机器已经执行了这个任务(并且成功执行了update查询)。在这种情况下,你忘记它并尝试下一个任务,因为你知道这一个已经被执行。



另一个我在这里使用CAS的想法是非常动态的(我使用它主要是为了管理连接,即套接字,但它足够通用以保存任何种类的资源)。基本上,它计算它持有多少资源。当你尝试获取一个资源,它读取计数器,递减它,尝试更新它(如果没有其他修改计数器之间),如果这是成功的,那么你可以简单地从池中取出一个资源,借给它(一旦计数器达到0,它将不借出资源)。如果我发布此代码,我将在此添加一个链接。


I have been reading about volatile and synchronized in Java but have been scratching my head in confusion. I am hoping someone can help me clear up a problem

private HashMap<String,int> map = new HashMap<String,int>();

In my thread

if (map.get("value") == null)
{
map.put("value",0);
}
map.put("value",map.get("value")+1);

My goal is to have all the threads share this map. If I add volatile it doesn't seem to fix the problem for me (I output and see that map is being override to each time). I then tried using ConcurrentHashMap and adding volatile in front of that... that also didn't seem to work. Based on my reading about volatile my understanding is that it should "lock" access to the map when map is being written to and then when map is done being written to the lock is released.

So... then I tried adding static

private static ConcurrentHashMap<String,int> map = new ConcurrentHashMap<String,int>();

and that seems to work perfectly... But... I keep reading that using static isn't the right way due to something about 'contention' (which I don't quite understand)

Thanks in advance

解决方案

Volatile won't help here. Volatile is useful to solve visibility problems, but you are facing another problem: atomicity.

Oh, and volatile has absolutely nothing to do with locking. It won't acquire a lock when reading/writing, it won't release anything ever. What it does is this: all the actions that happened-before a write to a volatile field will be visible to every other thread, after they read the same volatile field. There's no locking involved (they are similar in that the memory effects of releasing/acquiring a lock are exactly the same).

The operations get and set are not atomic, meaning that other things may happen between the two.

For instance, one thread will get the value, then ANOTHER thread will get the same value, both will increment the value, then the first will set the new value, then the second will do the same. The final result is not what you expected.

The most common solution to this problem is to serialize access (ie, synchronize) to the shared variable, or to use compare-and-set (CAS) (so you won't need to do synchronization).

1. synchronized

private final Map<String, Integer> m = new ConcurrentHashMap<String, Integer>();
synchronized incrementValue(final String valueName) {
  m.put(valueName, m.get(valueName) + 1);
}

Note that if you use this solution then EVERY ACCESS to the map must synchronize on the same lock.

2. CAS

Many CAS algorithms are already implemented in the JVM, in a very performatic way (ie, they use native code, and the JIT may use instructions specific to the processor, that you cannot access in other ways -- check the class Unsafe in Sun's JVM for example).

One class that might be useful to you here is AtomicInteger. You can use it like this:

private final Map<String, AtomicInteger> m = new ConcurrentHashMap<String, AtomicInteger>();
incrementValue(final String valueName) {
  m.get(valueName).incrementAndGet();
}

What a CAS algorithm will do is something like this:

for (;;) {
  state = object.getCurrentState();
  if (object.updateValueAndStateIfStateDidntChange(state)) {
    break;
  }
}

It is assumed that the method updateValueAndStateIfStateDidntChange is atomic and will return true only if it was able to update the value. This way, if another thread modifies the value after you get the state and before you update the value, the method will return false and the loop will try it again.

Assuming you can implement that method in such a way that won't use synchronized (and you can, through the use of classes in java.util.concurrent), you will avoid contention (which means threads waiting to obtain locks held by another threads) and you may see a general improvement in performance.

I use a lot this kind of thing in distributed task execution system I wrote. The tasks must all be executed exactly once, and I have lots of machines executing tasks. The tasks are all specified in a single MySQL table. How to do it? You must have a column which purpose is to allow the implementation of CAS. Call it executing. Before starting the task, you must do something like: retrieve the next task, "update tasks set executing = 1 where id = :id AND executing = 0" and count the number of updated lines. If you updated 0 lines, it is because another thread/process/machine has already taken that task (and successfully executed that "update" query); in this case, you forget it and try the next task, because you know that this one is already being executed. If you updated 1 line, then it is good to go, you can execute it.

Another place where I use a lot this idea of CAS is in a very dynamic (in respect to its configuration) resource pool I wrote (I use it mostly to manage "connections", ie, sockets, but it is sufficiently generic to hold any kind of resource). Basically, it counts how many resources it is holding. When you try to acquire a resource, it reads the counter, decrements it, tries to update it (if nothing else modified the counter in between), and if this is successful, then you can simply take a resource from the pool and lend it (once the counter reaches 0, it won't lend a resource). If I ever publish this code, I will be certain to add a link to it here.

这篇关于Java并发增加值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆