简体   繁体   中英

Share local variable value between barrier threads in java

I've been working on implementing a custom Cyclic Barrier which adds values passed into the await method and returns the sum to all threads when after notify is called.

The code:

public class Barrier {

    private final int parties;
    private int partiesArrived = 0;

    private volatile int sum = 0;
    private volatile int oldSum = 0;

    public Barrier(int parties) {
        if (parties < 1) throw new IllegalArgumentException("Number of parties has to be 1 or higher.");
        this.parties = parties;
    }

    public int getParties() { return parties; }

    public synchronized int waitBarrier(int value) throws InterruptedException {
        partiesArrived += 1;
        sum += value;
        if (partiesArrived != parties) {
            wait();
        }
        else {
            oldSum = sum;
            sum = 0;
            partiesArrived = 0;
            notifyAll();
        }
        return oldSum;
    }

    public int getNumberWaiting() { return partiesArrived; }
}

This works, but I hear that there is a way to change the values sum and oldSum (or at least oldSum ) into local variables of the waitBarrier method. However, after racking my head over it, I don't see a way.

Is it possible and , if yes, how?

You can return sum and have the first party clear it:

public synchronized int waitBarrier(int value) throws InterruptedException {
    if (partiesArrived == 0) {
        sum = 0;
    }

    partiesArrived++;
    sum += value;

    if (partiesArrived == parties) {
        notifyAll();
    } else {
        while (partiesArrived < parties) {
            wait();
        }
    }

    return sum;
}

Note that the wait condition should always be checked in a loop in case of spurious wakeups . Also, sum doesn't need to be volatile if it's not accessed outside the synchronized block.

However, after racking my head over it, I don't see a way.

Quite so.

Is it possible and , if yes, how?

it is not possible.

For some proof:

Try marking a local var as volatile . It won't work: The compiler doesn't allow it. Why doesn't it? Because volatile is neccessarily a no-op: local vars simply cannot be shared with another thread .

One might think this is 'sharing' a local:

void test() {
   int aLocalVar = 10;
   Thread t = new Thread(() -> {
     System.out.println("Wow, we're sharing it! " + aLocalVar);
   });
   t.start();
}

But it's some syntax sugar tripping you up there: Actually (and you can confirm this with javap -c -v to show the bytecode that javac makes for this code), a copy of the local var is handed to the block here. This then explains why, in java, the above fails to compile unless the variable you're trying to share is either [A] marked final or [B] could have been so marked without error (this is called 'the variable is effectively final'). Had java allowed you to access non-(effectively) finals like this, and had java used the copy mechanism that is available, that would be incredibly confusing.

Of course, in java, all non-primitives are references. Pointers, in the parlance of some other languages. Thus, you can 'share' (not really, it'll be a copy) a local var and nevertheless get what you want (share state between 2 threads), because whilst you get a copy of the variable, the variable is just a pointer. It's like this: If I have a piece of paper and it is mine, but I can toss it in a photocopier and give you a copy too, we can't, seemingly, share state. Whatever I scratch on my paper won't magically appear on yours; it's not voodoo paper. But, if there is an address to a house on my paper and I copy it and hand you a copy, it feels like we're sharing that: If you walk over to the house and, I dunno, toss a brick through a window, and I walk over later, I can see it.

Many objects in java are immutable (impervious to bricks), and the primitives aren't references. One solution is to use the AtomicX family which are just simplistic wrappers around a primitive or reference, making them mutable:

AtomicInteger v = new AtomicInteger();
Thread t = new Thread(() -> {v.set(10);});
t.start();
t.yield();
System.out.println(t.get());
// prints 10

But no actual sharing of a local happened here. The thread got a -copy- of the reference to a single AtomicInteger instance that lives on the heap, and both threads ended up 'walking over to the house', here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM