2011-11-18 79 views
2
public class counting 
{ 
    private static int counter = 0; 

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

方法counterCheck可以被我的应用程序中的多个线程访问。我知道静态变量不是线程安全的。我将不胜感激,如果有人可以帮助我的例子或给我理由为什么我必须同步方法或块。如果我不同步会发生什么?多线程应用程序中的静态计数器线程是否安全?

+4

这实际上是接近代码典型的例子是*不*线程。 –

+1

不是一个无关紧要的问题,而是两个无关的提示:(1)它不会编译[countCheck()']没有返回值。 (2)java有一个强大的命名规则,即**类名以大写字母**开头,您应该重命名:'counting' - >'Counting'。 – amit

+0

@Amit-这不是我的原始代码。感谢您的建议,但。 – questborn

回答

2

这不是线程安全的,从多个线程更新计数的这种模式可能是实现负缩放(它运行速度较慢,当你添加更多的线程)多线程应用程序的#1路。

如果添加必要的锁定以使此线程安全,则每个线程在计数时都会完全停止。即使您使用原子操作来更新计数器,您最终也会在更新计数器的每个线程之间跳动CPU缓存线。

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

6

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

+0

这非常有道理。 – questborn

0

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

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. 两个线程存储他们的柜台

在这种情况下,你希望计数器递增两次,但它只会增加一次。你可以把它想象成如果正在执行这个代码:

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; 

所以,你可以在一个​​块包装它,但使用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(); 
     } 
    } 
} 
0

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

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

或者你可以让一个线程进行一些修改,另一个线程总是看到0,因为由于Java内存模型,另一个线程可能会看到缓存在寄存器中的值。经验法则:每次某个共享状态被多个线程访问时,其中一个可以修改该共享状态,所有访问,甚至只读访问都必须使用同一个锁进行同步。

0

第一的counter++本身不是线程安全的

硬件的限制,使其等同于

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

,并在同一时间2个线程,会发生什么呢?一个更新会丢失,这就是

可以让这个线程安全用的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)); 
}