@Test
  public void registerAndParseAndSerializeInParallel_works() throws Exception {
    MutableSerializationRegistry registry = new MutableSerializationRegistry();
    ExecutorService threadPool = Executors.newFixedThreadPool(4);
    List<Future<?>> futures = new ArrayList<>();
    registry.registerKeySerializer(
        KeySerializer.create(
            MutableSerializationRegistryMultithreadTest::serializeKey1ToA,
            TestKey1.class,
            TestSerializationA.class));
    registry.registerKeyParser(
        KeyParser.create(
            MutableSerializationRegistryMultithreadTest::parseAToKey1,
            A_1,
            TestSerializationA.class));
    registry.registerParametersSerializer(
        ParametersSerializer.create(
            MutableSerializationRegistryMultithreadTest::serializeParameters1ToA,
            TestParameters1.class,
            TestSerializationA.class));
    registry.registerParametersParser(
        ParametersParser.create(
            MutableSerializationRegistryMultithreadTest::parseAToParameters1,
            A_1,
            TestSerializationA.class));

    futures.add(
        threadPool.submit(
            () -> {
              try {
                for (int i = 0; i < REPETITIONS; ++i) {
                  registry.registerKeyParser(
                      KeyParser.create(
                          MutableSerializationRegistryMultithreadTest::parseAToKey1,
                          Bytes.copyFrom(ByteBuffer.allocate(4).putInt(i).array()),
                          TestSerializationA.class));
                }
              } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
              }
            }));
    futures.add(
        threadPool.submit(
            () -> {
              try {
                // This thread mainly wants to do a key serializer registration, but we only have
                // one of those, since each needs either a new serialization class, or a new key
                // class. So first do a few parsing registrations to mix things up.
                for (int i = 0; i < REPETITIONS / 2; ++i) {
                  registry.registerKeyParser(
                      KeyParser.create(
                          MutableSerializationRegistryMultithreadTest::parseBToKey1,
                          Bytes.copyFrom(ByteBuffer.allocate(4).putInt(i).array()),
                          TestSerializationB.class));
                }
                registry.registerKeySerializer(
                    KeySerializer.create(
                        MutableSerializationRegistryMultithreadTest::serializeKey2ToA,
                        TestKey2.class,
                        TestSerializationA.class));
                registry.registerKeySerializer(
                    KeySerializer.create(
                        MutableSerializationRegistryMultithreadTest::serializeKey2ToB,
                        TestKey2.class,
                        TestSerializationB.class));
                registry.registerKeySerializer(
                    KeySerializer.create(
                        MutableSerializationRegistryMultithreadTest::serializeKey1ToB,
                        TestKey1.class,
                        TestSerializationB.class));
              } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
              }
            }));
    futures.add(
        threadPool.submit(
            () -> {
              try {
                for (int i = 0; i < REPETITIONS; ++i) {
                  Object unused = registry.parseKey(new TestSerializationA(A_1), ACCESS);
                }
              } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
              }
            }));
    futures.add(
        threadPool.submit(
            () -> {
              try {
                for (int i = 0; i < REPETITIONS; ++i) {
                  Object unused =
                      registry.serializeKey(new TestKey1(), TestSerializationA.class, ACCESS);
                }
              } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
              }
            }));
    // =============================== More threads doing the same thing, this time for parameters.
    futures.add(
        threadPool.submit(
            () -> {
              try {
                for (int i = 0; i < REPETITIONS; ++i) {
                  registry.registerParametersParser(
                      ParametersParser.create(
                          MutableSerializationRegistryMultithreadTest::parseAToParameters1,
                          Bytes.copyFrom(ByteBuffer.allocate(4).putInt(i).array()),
                          TestSerializationA.class));
                }
              } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
              }
            }));
    futures.add(
        threadPool.submit(
            () -> {
              try {
                // This thread mainly wants to do a key serializer registration, but we only have
                // one of those, since each needs either a new serialization class, or a new key
                // class. So first do a few parsing registrations to mix things up.
                for (int i = 0; i < REPETITIONS / 2; ++i) {
                  registry.registerParametersParser(
                      ParametersParser.create(
                          MutableSerializationRegistryMultithreadTest::parseBToParameters1,
                          Bytes.copyFrom(ByteBuffer.allocate(4).putInt(i).array()),
                          TestSerializationB.class));
                }
                registry.registerParametersSerializer(
                    ParametersSerializer.create(
                        MutableSerializationRegistryMultithreadTest::serializeParameters2ToA,
                        TestParameters2.class,
                        TestSerializationA.class));
                registry.registerParametersSerializer(
                    ParametersSerializer.create(
                        MutableSerializationRegistryMultithreadTest::serializeParameters2ToB,
                        TestParameters2.class,
                        TestSerializationB.class));
                registry.registerParametersSerializer(
                    ParametersSerializer.create(
                        MutableSerializationRegistryMultithreadTest::serializeParameters1ToB,
                        TestParameters1.class,
                        TestSerializationB.class));
              } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
              }
            }));

    futures.add(
        threadPool.submit(
            () -> {
              try {
                for (int i = 0; i < REPETITIONS; ++i) {
                  Object unused = registry.parseParameters(new TestSerializationA(A_1));
                }
              } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
              }
            }));
    futures.add(
        threadPool.submit(
            () -> {
              try {
                for (int i = 0; i < REPETITIONS; ++i) {
                  Object unused =
                      registry.serializeParameters(new TestParameters1(), TestSerializationA.class);
                }
              } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
              }
            }));

    threadPool.shutdown();
    assertThat(threadPool.awaitTermination(300, SECONDS)).isTrue();
    for (int i = 0; i < futures.size(); ++i) {
      futures.get(i).get(); // This will throw an exception if the thread threw an exception.
    }
  }
Thread safety tests for {@link MutableSerializationRegistry}.
java_src/src/test/java/com/google/crypto/tink/internal/MutableSerializationRegistryMultithreadTest.java