title: How to properly use CountDownLatch for testing specific thread interleaving
body: <p>I'm trying to test a particular thread interleaving where one thread pauses right after checking if a key exists in a map, then another thread inserts a value to the map, then the first thread continues and potentially overwrites the value in the map if there isn't some kind of synchronization.</p>
  <p>So I used 2 <code>CountDownLatch</code> objects to stop the main thread just before inserting the value to the map, to wait for the worker thread to call the containsKey method and then the worker thread waits for the main thread to finish inserting the value to the map after which it continues.</p>
  <p>The test works great if there is no synchronization and the expected exception does isn't thrown.</p>
  <p>However, when there is proper synchronization (by defining the <code>register</code> method as <code>synchronized</code> for example) the test would (obviously) become deadlocked if it weren't for the busy wait loop.</p>
  <p>My question is - is there a way <strong>without using a timeout or a busy wait loop</strong> to make a thread wait for another thread to release it, <strong>but only as long</strong> as the other thread itself is <strong>not blocked</strong>?</p>
  <pre><code>package authentication;
  
  import org.junit.jupiter.api.Test;
  
  import java.util.HashMap;
  import java.util.Map;
  import java.util.concurrent.*;
  
  import static org.junit.jupiter.api.Assertions.assertThrows;
  
  public class RegisterConcurrencyTest {
  
      static class AlreadyRegisteredException extends Exception {}
  
      static class Register {
          private final Map&lt;String, String&gt; map;
          public Register(Map&lt;String, String&gt; map) {
              this.map = map;
          }
          public void register(String name, String password) throws AlreadyRegisteredException {
              if (map.containsKey(name))
                  throw new AlreadyRegisteredException();
              map.put(name, password);
          }
      }
  
      static class MockHashMap&lt;K,V&gt; extends HashMap&lt;K,V&gt; {
          private final CountDownLatch waitForRegister, waitForContainsKey;
          private final Thread main;
          public MockHashMap(CountDownLatch waitForRegister, CountDownLatch waitForContainsKey, Thread main) {
              this.waitForRegister = waitForRegister;
              this.waitForContainsKey = waitForContainsKey;
              this.main = main;
          }
          private void busyWaitForRegister() {
              try {
                  while (waitForRegister.await(1, TimeUnit.MILLISECONDS)) {
                      if (main.getState() == Thread.State.BLOCKED)
                          break;
                  }
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
          }
          @Override
          public boolean containsKey(Object key) {
              boolean result = super.containsKey(key);
              if (!Thread.currentThread().getName().equals(&quot;main&quot;)) {
                  waitForContainsKey.countDown();
                  busyWaitForRegister();
              }
              return result;
          }
      }
  
      @Test
      void register() throws Throwable {
  
          ExecutorService exec = Executors.newSingleThreadExecutor();
          final CountDownLatch waitForRegister = new CountDownLatch(1);
          final CountDownLatch waitForContainsKey = new CountDownLatch(1);
          Map&lt;String, String&gt; map = new MockHashMap&lt;&gt;(waitForRegister, waitForContainsKey, Thread.currentThread());
          Register reg = new Register(map);
          Future&lt;?&gt; future = exec.submit(() -&gt; {
              try {
                  reg.register(&quot;Bob&quot;, &quot;123&quot;);
              } catch (AlreadyRegisteredException e) {
                  throw new RuntimeException(e);
              }
          });
          waitForContainsKey.await();
          assertThrows(AlreadyRegisteredException.class, () -&gt; reg.register(&quot;Bob&quot;, &quot;456&quot;));
          waitForRegister.countDown();
          future.get();
      }
  }
  </code></pre>