The synchronized keyword is used to lock the object. The object is locked inside the synchronized code. What does this mean for this object and your changes to its object reference? Only this object is locked for synchronization of an object. However, do not re-allocate the object reference of the locked object. So what will happen if this is done? Consider the following code to implement a Stack:
Class Stack
{
Private int StackSize = 10;
Private int [] intArr = new int [stackSize];
Private int index; // The next available location in the Stack.
Public void push (int val)
{
Synchronized (intArr ){
// If it is full, the array is reassigned (that is, our Stack ).
If (index = intArr. length)
{
StackSize * = 2;
Int [] newintArr = new int [stackSize];
System. arraycopy (intArr, 0, newintArr, 0, intArr. length );
IntArr = newintArr;
}
IntArr [index] = val;
Index ++;
}
}
Public int pop ()
{
Int retval;
Synchronized (intArr ){
If (index> 0)
{
Retval = intArr [index-1]; // retrieve the value,
Index --; // and reduce the number of Stack values by 1.
Return retval;
}
}
Throw new EmptyStackException ();
}
//...
}
This Code uses an array to implement a Stack. An array with an initial size of 10 is created to hold the integer. This class implements the push and pop methods to simulate the use of Stack. In the push method, if there is no more space in the array to hold the pushed value, the array is reassigned to create more buckets. (This class is intentionally not implemented using Vector. The basic types cannot be stored in the Vector .)
Note that this code must be accessed by multiple threads. Each time the push and pop Methods access the data of shared instances of this class, they are completed in the synchronized block. This ensures that multiple threads cannot concurrently access this array to generate incorrect results.
This code has a major drawback. It performs synchronous processing on the integer array object, which is referenced by the intArr of the Stack class. When the push method re-allocates this integer array, this disadvantage is revealed. In this case, the intArr object reference is re-specified to reference a new, larger integer array object. Note that this occurs during the execution of the synchronized block of the push method. This block is synchronized to the objects referenced by the intArr variable. Therefore, the locked objects in this Code are no longer used. Consider the following sequence of events:
Thread 1 calls the push method and obtains the lock of the intArr object.
Thread 1 is preemptible by thread 2.
Thread 2 calls the pop method. This method is blocked by trying to obtain the same lock held by the current thread 1 in the push method.
Thread 1 re-obtains control and re-allocates the array. The intArr variable now references a different variable.
The push method exits and releases its lock on the original intArr object.
Thread 1 calls the push method again and obtains the lock of the new intArr object.
Thread 1 is preemptible by thread 2.
Thread 2 acquires the object lock of the old intArr object and tries to access its memory.
Now thread 1 holds the lock for the new object referenced by intArr, and thread 2 holds the lock for the old object referenced by intArr. Because the two threads hold different locks, they can concurrently execute the synchronized push and pop methods, resulting in errors. Obviously, this is not the expected result.
This problem is caused by the re-allocation of the object reference of the locked object by the push method. When an object is locked, other threads may be blocked when the same object is locked. If you re-allocate the object reference of the locked object to another object, the suspension locks of other threads are for objects that are no longer relevant in the code.
You can modify this Code to remove the intArr variable synchronization and synchronize the push and pop methods. You can do this by adding the synchronized keyword as a method modifier. The correct code is as follows:
Class Stack
{
// Same as the previous one...
Public synchronized void push (int val)
{
// If it is null, the array of Integers (that is, our Stack) will be reassigned ).
If (index = intArr. length)
{
StackSize * = 2;
Int [] newintArr = new int [stackSize];
System. arraycopy (intArr, 0, newintArr, 0, intArr. length );
IntArr = newintArr;
}
IntArr [index] = val;
Index ++;
}
Public synchronized int pop ()
{
Int retval;
If (index> 0)
{
Retval = intArr [index-1];
Index --;
Return retval;
}
Throw new EmptyStackException ();
}
}
This modification changes the actually obtained lock. The obtained lock is for the object for which the method is called, rather than locking the object referenced by the intArr variable. Because the obtained lock is no longer targeted at the objects referenced by intArr, the code is allowed to re-specify the intArr object reference.