Why does read before write operation on CopyOnWriteArrayList or synchronizedList() cause inconsistencies?

58 Views Asked by At

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 ?

0

There are 0 best solutions below