Synchronization
Synchronization is required on mutable resources because the operations
of multiple threads can interleave. For example, the c++
operation may appear
to be atomic, but it is actually composed of three steps: read the value of c,
increment the value of c, and write the value of c. If
one thread reads the value
of c and enters a blocked state, a second thread could read the same value and
the counter would only be incremented by one. Synchronizing access to the c++
operation would force all three steps to be completed atomically.
Although synchronization can prevent thread
interference and memory
consistency errors, it can also degrade performance and open up the possibility
of a deadlock. A deadlock occurs when two threads wait for each to proceed.
The Synchronize Keyword
The synchronize keyword is used to designate
synchronized methods and
statements. A synchronized block of code is guarded by a lock that can only
be acquired by one thread at a time. In a synchronized static method,
the singleton Class object is implicitly used as the lock.
In a synchronized non-
static method, the object instance is implicitly used as the lock. In a
synchronized statement, the lock must be provided as an argument. Multiple
locks allow for more granular levels of synchronization.
For example, imagine a Counter class with a synchronized count() method.
If the count()
method was static, only one thread in the application could
increment the counter at a time. If the count() method was non-static,
multiple Counter objects could be instantiated and incremented concurrently.
If the Counter class had two separate variables and two separate count()
methods, each method could use synchronized statements
with different locks
so that both variables could be incremented concurrently.