how to unit test a synchronized method?
Asked Answered
K

3

13

Say I have such a method:

synchronized void incrementIndex() {
      index++;
}

I want to unit test this method to see whether index's final value is set correctly if multiple threads simultaneously attempt to increment index. Assuming I don't know about the "synchronized" keyword in the method's declaration (and I only know the contract of the method), how can I do the test?

p.s. I am using Mockito for writing test cases if it helps.

Kimber answered 17/11, 2014 at 6:47 Comment(1)
I'm not sure this is testable simply because your incrementIndex method is atomic. Synchronizing is only important if there are at least two steps that must be performed together such as two writes, two reads, or most typically a read and a write (the classic test and set problem). How would any test catch your incrementIndex() method failing to be synchronized?Inductile
H
9

You could test this by having multiple threads execute the method and then asserting that the result is what you would expect. I have my doubts about how effective and reliable this would be. Multithreaded code is notoriously difficult to test and it mostly comes down to careful design. I would definitely recommend adding tests that assert that the methods you expect to by synchronized actually have the synchronized modifier. See an example of both approaches below:

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

public class SyncTest {
  private final static int NUM_THREADS = 10;
  private final static int NUM_ITERATIONS = 1000;

  @Test
  public void testSynchronized() throws InterruptedException {
    // This test will likely perform differently on different platforms.
    ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
    final Counter sync = new Counter();
    final Counter notSync = new Counter();

    for (int i = 0; i < NUM_THREADS; i++) {
      executor.submit(new Runnable() {
        @Override
        public void run() {
          for (int i = 0; i < NUM_ITERATIONS; i++) {
            sync.incSync();
            notSync.inc();
          }
        }
      });
    }

    executor.shutdown();
    executor.awaitTermination(5, TimeUnit.SECONDS);
    assertThat(sync.getValue(), is(NUM_THREADS * NUM_ITERATIONS));
    assertThat(notSync.getValue(), is(not(NUM_THREADS * NUM_ITERATIONS)));
  }

  @Test
  public void methodIncSyncHasSynchronizedModifier() throws Exception {
    Method m = Counter.class.getMethod("incSync");
    assertThat(Modifier.isSynchronized(m.getModifiers()), is(true)); 
  }

  private static class Counter {
    private int value = 0;

    public synchronized void incSync() {
      value++;
    }

    public void inc() {
      value++;
    }

    public int getValue() {
      return value;
    }
  }
}
Hydnocarpate answered 17/11, 2014 at 7:35 Comment(3)
Before executor.awaitTermination() you should call executor.shutdown() first .Otherwise, you might be waiting for a very long time, since awaitTermination doesn't actually shut down your executor. (#18425526)Cordelier
What if the method only contains a synchronized block, so it doesn't have synchronized as a modifier? Is there any other way to test that, other than asserting for the "expected behaviour"?Brunette
@ToniNagy I think asserting behavior would be your only option. Since you're always testing for code correctness first anyway, I would default to testing expected behavior not the presence of synchronized modifier.Hydnocarpate
U
4

CandiedOrange is correct in his comment to your question. In other words, and given the method you mentioned, you should not worry about threadA calling the method at the same moment threadB is since both calls are writing to index. Had it been something like:

void incrementIndex() {
     index++;
     System.out.println(index); // threadB might have written to index
                                // before this statement is executed in threadA
}

threaA calls the method, increments index in the first statement, then attempts to read the value of index in the second statement, by which time threadB might have already made the call to the method and incremented index before threadA reads it and prints it. This is where synchronized is necessary to avoid such a situation.

Now, if you still want to test synchronization, and you have access to the method code (or maybe you can do a similar prototype), you may consider something like the following that illustrates how multithreading behaves with synchronized methods:

public void theMethod(long value, String caller) {
    System.out.println("thread" + caller + " is calling...");
    System.out.println("thread" + caller + " is going to sleep...");

    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("thread" + caller + " woke up!");
}

This should output:

threadA is calling...
threadA is going to sleep...
threadA woke up!
threadB is calling...
threadB is going to sleep...
threadB woke up!

Without the synchronized keyword, the output be:

threadA is calling...
threadA is going to sleep...
threadB is calling...
threadB is going to sleep...
threadA woke up!
threadB woke up!
Underlaid answered 26/1, 2015 at 6:49 Comment(0)
L
-2

Is. i++. Atomic?

No

So sync is justified if you care about the correctness of your program.

But testing is hard.

Visual inspection tells us that the nonatomic increment ooperation is protected and made atomic and all is well as far as we know, but we're don't know anything about the state of the rest of the system.

It is possible to test that a function is sychronised only by its side effects. There is a testable pattern that orgs the code such that you dependency inject the sync rather than using the Jave intrinsic, but if all there is is your original question then I would rely on visual inspection and obvious correctness.

Licensee answered 27/11, 2015 at 20:42 Comment(1)
https://mcmap.net/q/16372/-why-is-i-not-atomicToothed

© 2022 - 2024 — McMap. All rights reserved.