20

My IDE (JetBrains IntelliJ IDEA) warns me about synchronizing on a method parameter even when it's always an object.

enter image description here

The full warning reads:

Synchronization on method parameter 's' ... Inspection info: Reports synchronization on a local variable or parameter. It is very difficult to guarantee correctness when such synchronization is used. It may be possible to improve code like this by controlling access through e.g. a synchronized wrapper class, or by synchronizing on a field.

My guess is that with auto-boxing, the parameter might be a primitive which gets converted to an object? Although, with auto-boxing, I would assume it's always an object, but maybe not a shared object which means it wouldn't be shared synchronization.

Anyone know why the warning would be present? In my case ShortCircuit type is always an object and the IDE should be able to know that.

LazyOne
  • 158,824
  • 45
  • 388
  • 391
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817

2 Answers2

12

The thing is that if you forget to synchronize on ShortCircuit when using it in other places of your code you might get unpredictable results. It's a lot better to synchronize inside the ShortCircuit class so it's guaranteed to be thread safe.

Update

If you're moving the synchronization outside of the class it's inherently unsafe for threading. If you want to synchronize on it externally you will have to audit all places it's used, that's why you get the warning. It's all about good encapsulation. It will be even worse if it is in a public API.

Now if you move the fireFinalCallback method to your ShortCircuit class you can guarantee that the callback won't fire simultaneously. Otherwise you need to have this in mind when calling the methods on that class.

jontro
  • 10,241
  • 6
  • 46
  • 71
  • @AlexanderMills updated the answer. It's hard to give an example since it's more about encapsulation than a short example can show. – jontro Feb 19 '19 at 21:38
  • I twitched inside when I read "undefined behaviour". While this is true, given a sufficiently broad interpretation of the term, "undefined behaviour" is a term that has such strong (and disastrous!) implications in languages like C++ that I'd rather talk about "unpredictable results" or "unspecified results" or something similar. – Marco13 Feb 20 '19 at 19:43
7

As jontro already mentioned in his answer (and basically, as the warning already says) : This sort of synchronization on the ShortCircuit object does not have the effect that the developer probably hoped to achieve. Unfortunately, the tooltip in your screenshot hides the actual code, but it seems like the code could roughly be

synchronized (s)
{
    if (!s.isFinalCallbackFired())
    {
        s.doFire();
    }
}

That is: It is first checked whether isFinalCallbackFired returns false, and if this is the case, something (hidden) is done, which likely causes the isFinalCallbackFired state to switch to true.

So my assumption is, roughly, that the goal of putting the if statement into the synchronized block was to make sure that doFire is always called exactly once.


And indeed, at this point, the synchronization could be justified. More specifically, and a bit oversimplified:

What can be guaranteed:

When two threads are executing the fireFinalCallback method with the same ShortCircuit parameter, the synchronized block will guarantee that only one thread at a time can check the isFinalCallbackFired state and (if it is false) call the doFire method. So it is guaranteed that doFire will be called only once.

What can not be guaranteed:

When one thread is executing the fireFinalCallback method, and another thread does any operation on the ShortCircuit object (like calling doFire), then this might lead to an inconsistent state. Particularly, if another thread also does

if (!s.isFinalCallbackFired())
{
    s.doFire();
}

but without synchronizing on the object, then doFire may be called twice.


The following is an MCVE that illustrates the effect:

public class SynchronizeOnParameter
{
    public static void main(String[] args)
    {
        System.out.println("Running test without synchronization:");
        runWithoutSync();
        System.out.println();

        System.out.println("Running test with synchronization:");
        runWithSync();
        System.out.println();

        System.out.println("Running test with wrong synchronization:");
        runWithSyncWrong();
        System.out.println();

    }

    private static void runWithoutSync()
    {
        ShortCircuit s = new ShortCircuit();
        new Thread(() -> fireFinalCallbackWithoutSync(s)).start();
        pause(250);
        new Thread(() -> fireFinalCallbackWithoutSync(s)).start();
        pause(1000);
    }

    private static void runWithSync()
    {
        ShortCircuit s = new ShortCircuit();
        new Thread(() -> fireFinalCallbackWithSync(s)).start();
        pause(250);
        new Thread(() -> fireFinalCallbackWithSync(s)).start();
        pause(1000);
    }

    private static void runWithSyncWrong()
    {
        ShortCircuit s = new ShortCircuit();
        new Thread(() -> fireFinalCallbackWithSync(s)).start();

        if (!s.isFinalCallbackFired())
        {
            s.doFire();
        }
    }



    private static void fireFinalCallbackWithoutSync(ShortCircuit s)
    {
        if (!s.isFinalCallbackFired())
        {
            s.doFire();
        }
    }

    private static void fireFinalCallbackWithSync(ShortCircuit s)
    {
        synchronized (s)
        {
            if (!s.isFinalCallbackFired())
            {
                s.doFire();
            }
        }
    }

    static class ShortCircuit
    {
        private boolean fired = false;

        boolean isFinalCallbackFired()
        {
            return fired;
        }

        void doFire()
        {
            System.out.println("Calling doFire");
            pause(500);
            fired = true;
        }
    }

    private static void pause(long ms)
    {
        try
        {
            Thread.sleep(ms);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

}

The output is

Running test without synchronization:
Calling doFire
Calling doFire

Running test with synchronization:
Calling doFire

Running test with wrong synchronization:
Calling doFire
Calling doFire

So the synchonized block does make sure that the doFire method is only called once. But this only works if all modifications are only done in the fureFinalCallback method. If the object is modified elsewhere, without a synchronized block, the doFire method may be called twice.

(I'd like to offer a solution for this, but without details about the ShortCircuit class and the remaining classes and processes, one could only give the vague hint to have a look at the java.util.concurrent package and its subpackages: Locks and Conditions might be a viable path, but you have to figure that out...)

Marco13
  • 53,703
  • 9
  • 80
  • 159