Java:多个线程通过同步方法递减静态变量,在同一时间访问变量。

6

我试图同步我的Person类方法,以便我的静态计数器变量在一个线程中递减。

public class Person extends Thread {

    private static int count = 10;

    public void decrement() {
        synchronized(Person.class) {
            count--;
        }

    }

    public int getCount() {
        return count;
    }

    public void run(){
        while( count > 0){
            this.decrement();
            System.out.print(this.getCount() + ",");
        }
    }
}

这是我的主类。每个线程都会通过同步方法递减静态计数器,以避免多个线程访问同一资源。

public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        Person p1 = new Person();
        Person p2 = new Person();
        Person p3 = new Person();
        Person p4 = new Person();
        Person p5 = new Person();

        p1.start();
        p2.start();
        p3.start();
        p4.start();
        p5.start();
    }
}

但是当我运行我的程序时,它打印了重复的计数器值。我做错了什么?

输出:

8,8,7,6,5,4,3,2,1,0

8,7,5,3,1,0,6,8,4,0
3个回答

5
在原始代码中所发生的是以下内容:
  1. 线程一减少计数(count -> 9)
  2. 线程二减少计数(count -> 8)
  3. 线程三减少计数(count -> 7)
  4. 线程四减少计数(count -> 6)
  5. 线程一输出计数(count: 6)
  6. 线程二输出计数(count: 6)
  7. 线程三输出计数(count: 6)
  8. 线程四输出计数(count: 6)
由于你锁定了减量而不是同时锁定减量和输出,因此它似乎会多次减量。
换句话说,不能保证此代码将连续执行:
        this.decrement();
        System.out.print(this.getCount() + ",");

以下是修正后的代码。在减少计数时,它返回当前计数值,以便可以返回和打印新值。

public class Person extends Thread {

    private static int count = 10;

    public int decrement() {
        synchronized(Person.class) {
            count = count - 1;
            return count;
        }

    }

    public int getCount() {
        synchronized(Person.class) {
            return count;
        }
    }

    public void run(){
        while( getCount() > 0){
            int count = this.decrement();
            System.out.println(count);
        }
    }
}

我建议使用AtomicInteger来完成这个任务:

import java.util.concurrent.atomic.AtomicInteger;

public class Person extends Thread {

    private static AtomicInteger count = new AtomicInteger(10);

    public int decrement() {
        return count.decrementAndGet();
    }

    public void run(){
        while(count.get() > 0){
            int currentCount = this.decrement();
            System.out.print(currentCount + ",");
        }
    }
}

第二个没有原子整数的例子仍然在同时访问计数器并输出重复项。输出:8,6,4,7,2,8,0,1,3,5 - Waleed Ahmad
有趣。我之前以为它能正常工作,但现在我发现了一个错误。已更新。 - Jason
两个示例都可能减到零以下,因为在循环条件中读取和递减之间也可能存在交错。 - Hulk

3

仅同步写操作是不够的,您还需要同步Getter方法(否则读线程可能会读取一个过期的值)。但在这种情况下,问题在于其他线程可以在线程将值减少和该线程检索值之间交错执行。

使用java.util.concurrent.atomic.AtomicInteger来存储计数。但是,如果您为递减和获取保留单独的方法(分别锁定递减和锁定Getter方法),仍然不能保证线程不会以导致重复写出的方式交错。使用AtomicInteger的decrementAndGet方法确保递减的值是返回的值。


你是怎么知道的? - logger
@user3659052:希望新增的解释有所帮助。 - Nathan Hughes

1
适当的同步是关键,但使用 AtomicInteger 并不是全部答案。你需要意识到每个线程都需要报告刚刚减少的计数,即使你正在 1)使用 AtomicInteger 或者 2)正确(分别)同步 decrementgetCount 方法。 while 循环的主体是一个关键部分,这是一个不能被中断的代码部分。
public void run(){
    while( count > 0){
        synchronized (Person.class)
        {
            decrement();
            System.out.print(getCount() + ",");
        }
    }
}

输出:

9,8,7,6,5,4,3,2,1,0,-1,-2,-3,-4

有时它会停在-3。现在,每个实例都可以继续减少,因为它检查了通过的while条件,然后线程中断,然后另一个线程减少。然后原始线程即使已经降到0也会减少!请检查循环内部。
public void run(){
    while( count > 0){
        synchronized (Person.class)
        {
            if (count > 0)
            {
                decrement();
                System.out.print(getCount() + ",");
            }
        }
    }
}

请注意,使用AtomicInteger#updateAndGet和仅在值大于0时递减该值的运算符也可以实现此操作。 - KookieMonster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接