Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Synchronized and Concurrent Collections are thread-safe but their content not

From this source one can read:

It's worth mentioning that synchronized and concurrent collections only make the collection itself thread-safe and not the contents.

I thought if Collection is thread-safe then its content will implicitly be thread-safe.

I mean if two threads cannot access my Collection object then the object which my Collection object is holding will implicitly become thread-safe.

I missing the point, could someone please explain me with an example?

like image 819
pjj Avatar asked Feb 05 '21 08:02

pjj


People also ask

Is synchronized collection thread-safe?

Synchronized collections achieve thread-safety through intrinsic locking, and the entire collections are locked. Intrinsic locking is implemented via synchronized blocks within the wrapped collection's methods.

What makes concurrent collections different from synchronized collections?

The main reason for this slowness is locking; synchronized collections lock the whole collection e.g. whole Map or List while concurrent collection never locks the whole Map or List. They achieve thread safety by using advanced and sophisticated techniques like lock stripping.

Which collection classes are synchronized or thread-safe?

The only two legacy collections are thread-safe: Vector and Hashtable.

How can collections be thread-safe?

This method accepts an object of Set interface and, returns a synchronized (thread-safe) set backed by the specified set. This method accepts an object of the Map interface and, returns a synchronized (thread-safe) sorted map backed by the specified sorted map.


Video Answer


2 Answers

tl;dr

The objects stored in a thread-safe collection can be leaked outside and used in a non-thread-safe manner.

Detailed Answer:

I thought if Collection is thread-safe then its content will implicitly be thread-safe, I mean if two threads cannot access my Collection object then the object which my Collection object is holding will implicitly become thread-safe.

I know sure I missing the point, could someone please explain me with an example.

Consider the following code that uses two threads to add to the same non-thread-safe list the elements from 0 to 10. In the end, the main thread sums all the elements of that list. The final result should be same as 0 + 0 + 1 + 1 + ... 9 + 9 = 90. However, if you execute the code a couple of times you get different values, and sometimes even the following NPE:

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.stream.ReduceOps$1ReducingSink.accept(ReduceOps.java:80)
    at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:553)
    at Z.CollectionThreadSafe.main(CollectionThreadSafe.java:26)

All this is the result of the race-condition during the call of the method add.

private static void addToList(List<Integer> list) {
    for (int i = 0; i < 10; i++)
        list.add(i);
}

public static void main(String[] arg) throws InterruptedException {
    final int TOTAL_THREADS = 2;
    List<Integer> list = new ArrayList<>();
    ExecutorService pool = Executors.newFixedThreadPool(TOTAL_THREADS);
    for (int i = 0; i < TOTAL_THREADS; i++) {
        pool.submit(() -> addToList(list));
    }
    pool.shutdown();
    pool.awaitTermination(10, TimeUnit.SECONDS);
    System.out.println(list.stream().reduce(0, Integer::sum));
}

Let us fix the race-condition by using a thread-Safe List by calling Collections.synchronizedList. So let us adapt the previous code to:

List<Integer> list = Collections.synchronizedList(new ArrayList<>());

You can run it as many times as you want; the final result is always the same i.e., 90. That much we knew already. Let us showcase the:

It's worth mentioning that synchronized and concurrent collections only make the collection itself thread-safe and not the contents.

You just need to adapt the previous code from:

List<Integer> list = Collections.synchronizedList(new ArrayList<>());

to:

final List<List<Integer>> LIST_THREAD_SAFE = Collections.synchronizedList(new ArrayList<>());
LIST_THREAD_SAFE.add(new ArrayList<>());
List<Integer> list = LIST_THREAD_SAFE.get(0);
...

and voilá! you have exactly the same situation as the first example that we have showcased (i.e., race-condition). Even though the list LIST_THREAD_SAFE is thread-safe its content is not. Hence,

synchronized and concurrent collections only make the collection itself thread-safe and not the contents.

like image 79
dreamcrash Avatar answered Oct 14 '22 01:10

dreamcrash


If the collection is thread safe two thread can still access the collection simultaneously. They also could do modification simultaneously if this does not influence the the coherence of the collection. It is guaranteed only that all operation are consistent possibly with the minimum amount of locks. This has no relation with the object in the collection: one thread can get a reference of an object from the collection and keep for a certain amount of time and so other threads can get the same reference from the collection (in the same time o later) and access the object in the same time.

like image 43
Andrea Avatar answered Oct 14 '22 01:10

Andrea