I'm trying to understand the difference in behaviour of an ArrayList and a Vector. Does the following snippet in any way illustrate the difference in synchronization ? The output for the ArrayList (f1) is unpredictable while the output for the Vector (f2) is predictable. I think it may just be luck that f2 has predictable output because modifying f2 slightly to get the thread to sleep for even a ms (f3) causes an empty vector ! What's causing that ?

4 Answers
4

To answer the question: Yes vector is synchronized, this means that concurrent actions on the data structure itself won't lead to unexpected behavior (e.g. NullPointerExceptions or something). Hence calls like size() are perfectly safe with a Vector in concurrent situations, but not with an ArrayList (note if there are only read accesses ArrayLists are safe too, we get into problems as soon as at least one thread writes to the datastructure, e.g. add/remove)

What you want here is to add a number to your datastructure depending on the current size (ie if N threads execute this, in the end we'd want the list to contain the numbers [0..N)). Sadly that does not work:

Assume that 2 threads execute this code sample concurrently on an empty list/vector. The following timeline is quite possible:

Both execute size() and get back the value 0. They then go into the true branch of the and both add 0 to the datastructure. That's not what you want.

Hence you'll have to synchronize in your business logic anyhow to make sure that size() and add() are executed atomically. Hence the synchronization of vector is quite useless in almost any scenario (contrary to some claims on modern JVMs the performance hit of an uncontended lock is completely negligible though, but the Collections API is much nicer so why not use it)

@Raj You're not even calling f3 anywhere in your code? Also there's no output anywhere either. Apart from the slight chance of some exceptions in the ArrayList code the general race condition is the same for both programs. Although in practice the locking, etc. may change observable behavior - that's obviously implementation (and luck) dependent.
–
VooMay 11 '12 at 20:13

Replace the call to f2 with a call to f3. From the original question: "modifying f2 slightly to get the thread to sleep for even a ms (f3)". I understand that the race condition exists for both. I'll look at it some more. Thanks for your help.
–
SridharMay 11 '12 at 20:20

@Raj Ah ok. Well the sleep only makes the race condition more obvious. Basically the sleep "guarantees" (not by the spec but in practice) that another thread is scheduled which then leads to the demonstrated problem. Without the sleep it's extremely unlikely that a thread is preempted exactly between executing size and add. If you ran it long enough it would probably happen sooner or later though. Could take a day, a week or 10ms though - that's the biggest problem with race conditions ;)
–
VooMay 11 '12 at 20:23