Java: Multiple threads decrementing static variable via synchronized method accessing variable at the same time
Asked Answered
A

3

6

I'm trying to synchronize my Person class Methods so that my static counter variable gets decremented by one thread at the a time.

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() + ",");
        }
    }
}

Here's my main class. Each thread will decrement to static counter via synchronized method to avoid mutiple thread access to same resource.

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();
    }
}

But when i run my program, it is printing duplicate counter values. What am i doing wrong?

Outputs:

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

8,7,5,3,1,0,6,8,4,0
Aarhus answered 20/4, 2016 at 19:10 Comment(0)
G
5

What's going on in the original code is the following:

  1. Thread one decrements count (count -> 9)
  2. Thread two decrements count (count -> 8)
  3. Thread three decrements count (count -> 7)
  4. Thread four decrements count (count -> 6)
  5. Thread one outputs count (count: 6)
  6. Thread two outputs count (count: 6)
  7. Thread three outputs count (count: 6)
  8. Thread four outputs count (count: 6)

Since you are locking the decrement and not the decrement and output together, it's appearing to decrement multiple times.

In other words, there is no guarantee that this code will execute back-to-back:

        this.decrement();
        System.out.print(this.getCount() + ",");

Here's the fixed code. It returns the current count value on decrement so that the new value can be returned and printed.

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);
        }
    }
}

I'd recommend AtomicInteger though for this task:

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 + ",");
        }
    }
}
Galinagalindo answered 20/4, 2016 at 19:19 Comment(3)
Second example without atom integer is still accessing counter at the same time outputting duplicates. Output : 8,6,4,7,2,8,0,1,3,5Aarhus
Interesting. I thought it was working earlier, but now I see the bug. Updated.Galinagalindo
Both examples may decrement below zero, because there may also be interleavig between the read in the loop condition and the decrement.Rosemarie
A
3

It's not enough to synchronize only writes, you have to synchronize the getter as well (otherwise the reader thread may read a stale value). But in this case the problem is that other threads can interleave executing between the time a thread decrements and the time that same thread retrieves a value.

Use a java.util.concurrent.atomic.AtomicInteger to store the count. But if you keep separate methods for the decrementing and the getting (locking the decrement and lock the getter separately), still there's nothing to guarantee that threads don't interleave in a way that causes duplicates to get written out. Using AtomicInteger's decrementAndGet method makes sure that the value that's decremented is the one that's returned.

Associative answered 20/4, 2016 at 19:16 Comment(2)
how do you know this?Mouseear
@user3659052: hope the added explanation helps.Associative
H
1

Proper synchronization is the key, but using an AtomicInteger isn't the entire answer. What you need to realize is that each thread needs to report on the just-decremented count, which may be changed by another thread even if you're 1) using an AtomicInteger or 2) properly (separately) synchronizing both the decrement and getCount methods.

The body of the while loop is a critical section, a section of code that must not be interrupted.

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

Output:

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

Sometimes it stops at -3. Now, each instance is free to keep going and decrement, because it checks the while condition which passes, then the thread is interrupted, then another thread decrements. Then the original thread decrements even though it's already down to 0! Check inside the loop.

public void run(){
    while( count > 0){
        synchronized (Person.class)
        {
            if (count > 0)
            {
                decrement();
                System.out.print(getCount() + ",");
            }
        }
    }
}
Homburg answered 20/4, 2016 at 19:29 Comment(1)
Noting that this can also be accomplished with AtomicInteger using AtomicInteger#updateAndGet and an operator that only decrements the value if it is larger than 0.Monadelphous

© 2022 - 2024 — McMap. All rights reserved.