I am studying concurrency and I want to understand why a read before write operation on either: CopyOnWriteArrayList or synchronizedList() causes non-deterministic behavior in a multi thread scenario.
The code looks like this:
public class LoadBalancer {
private static final Integer MAX_PROVIDERS_SIZE = 10;
private final List<String> registeredProviders;
private LoadBalancer(List<String> initialProviders) {
if (!(initialProviders.size() == new HashSet<>(initialProviders).size())) {
throw new ProviderAlreadyExists();
}
this.registeredProviders = Collections.synchronizedList(initialProviders);
}
public static LoadBalancer of(List<String> registeredProviders) {
return new LoadBalancer(registeredProviders);
}
public LoadBalancer registerProvider(String provider) {
if (registeredProviders.size() >= MAX_PROVIDERS_SIZE) {
throw new ProvidersLengthExceeded();
} else {
registeredProviders.stream().filter(p -> p.equals(provider))
.findFirst()
.ifPresent(p -> {
throw new ProviderAlreadyExists();
});
registeredProviders.add(provider);
System.out.println("SIZE" + registeredProviders.size());
}
return this;
}
public List<String> getRegisteredProviders() {
return registeredProviders;
}
}
public class ProvidersLengthExceeded extends RuntimeException{
public ProvidersLengthExceeded() {
super("The maximum providers size is 10.");
}
}
public class ProviderAlreadyExists extends RuntimeException{
public ProviderAlreadyExists() {
super("The provider already exists.");
}
}
And this is the test:
@Test
public void concurrencyCheck() throws InterruptedException {
List<String> testProviders = IntStream.rangeClosed(0, 8).mapToObj(index -> index + "")
.collect(Collectors.toList());
LoadBalancer lb = LoadBalancer.of(testProviders);
ExecutorService service = Executors.newFixedThreadPool(10);
IntStream.range(0, 100).forEach(i -> service.submit(() -> {
lb.registerProvider(UUID.randomUUID().toString());
}));
Thread.sleep(10_000);
System.out.println(lb.getRegisteredProviders().size());
}
}
My desired behavior is that I should not be able to add more than MAX_PROVIDERS_SIZE items to the list.
With the code above I am getting the following output:
SIZE11
SIZE10
SIZE13
SIZE12
13
If I comment the filtering part before add, I get the correct output:
SIZE10
10
Why does that happen ?