多线程应用程序中的静态计数器线程是否安全?

时间:2011-11-18 23:11:12

标签: java multithreading synchronization

public class counting
{
  private static int counter = 0;

  public void boolean counterCheck(){
  counter++;
  if(counter==10)
  counter=0;
  }
}

方法counterCheck可以由我的应用程序中的多个线程访问。我知道静态变量不是线程安全的。如果有人可以通过示例帮助我,或者告诉我为什么必须同步方法或阻止,我将不胜感激。如果我不同步会发生什么?

6 个答案:

答案 0 :(得分:6)

显然不是线程安全的。考虑两个完全并行运行的线程。如果计数器是9,它们都会递增计数器,导致计数器为11.然后它们都不会看到计数器等于10,所以计数器将从那时开始递增,而不是按预期包装。

答案 1 :(得分:2)

这不是线程安全的, AND 这种从多个线程更新计数的模式可能是实现负扩展(在添加更多线程时运行速度较慢)的第一种方式线程应用程序。

如果你添加必要的锁定以使这个线程安全,那么每个线程将在计数时完全停止。即使您使用原子操作来更新计数器,最终也会在更新计数器的每个线程之间弹出CPU缓存行。

现在,如果每个线程操作在更新计数器之前需要相当长的时间,则这不是问题。但是如果每个操作都很快,计数器更新将序列化操作,导致所有线程上的一切都变慢。

答案 2 :(得分:0)

最大的危险?在counter检查之前,counter == 10两次递增,使重置为0永远不会发生。

答案 3 :(得分:0)

想象一下counter是9。

线程1执行此操作:

counter++; // counter = 10

线程2执行此操作:

counter++; // counter = 11
if(counter==10) // oops

现在,您可能认为可以通过以下方式解决此问题:

if(counter >= 10) counter -= 10;

但是现在,如果两个线程检查条件并发现它是真的会发生什么,那么两个线程都会将计数器递减10(现在你的计数器是负数)。

或者在更低级别,counter++实际上是三个操作:

  • 获取counter
  • counter
  • 中添加一个
  • 商店counter

所以:

  1. 线程1获得计数器
  2. 线程2获得计数器
  3. 两个主题都将一个添加到他们的计数器
  4. 两个线程都存储他们的计数器
  5. 在这种情况下,您希望计数器增加两次,但它只增加一次。您可以想象它好像正在执行此代码:

    c1 = counter;
    c2 = counter;
    c1 = c1 + 1;
    c2 = c2 + 1;
    counter = c1; // Note that this has no effect since the next statement overrides it
    counter = c2;
    

    因此,您可以将其包装在synchronized块中,但如果您只有几个线程,则使用AtomicInteger会更好:

    public class counting {
        private static AtomicInteger counter = new AtomicInteger(0);
    
        public static void counterCheck() {
            int value = counter.incrementAndGet();
            // Note: This could loop for a very long time if there's a lot of threads
            while(value >= 10 && !counter.compareAndSet(value, value - 10)) {
                value = counter.get();
            }
        }
    }
    

答案 4 :(得分:0)

由于多种原因,它不是线程安全的。最明显的一个是你可以有两个线程从9到11,正如其他答案所提到的那样。

但是由于counter ++不是原子操作,你也可以让两个线程读取相同的值并在之后递增到相同的值。 (意思是两个调用实际上只增加1)。

或者你可以让一个线程进行多次修改,而另一个线程总是看到0,因为由于Java内存模型,另一个线程可能会看到一个缓存在寄存器中的值。

良好的经验法则:每次多个线程访问某些共享状态,并且其中一个易于修改此共享状态时,所有访问,甚至是只读访问都必须使用相同的锁同步。

答案 5 :(得分:0)

counter++中的第一个本身并非线程安全

硬件限制使其等同于

int tmp = counter;
tmp=tmp+1;
counter=tmp;

当两个线程同时存在时会发生什么?一个更新丢失,这是什么

你可以使用atomicInteger和CAS循环使这个线程安全

private static AtomicInteger counter = new AtomicInteger(0);

public static boolean counterCheck(){
    do{
        int old = counter.get();
        int tmp = old+1;
        if(tmp==10)
            tmp=0;
        }
    }while(!counter.compareAndSet(old,tmp));
}