Voluntarily Relinquishing the Processor: Thread.yield()

Chapter 7 - Concurrent Access to Objects and Variables

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

synchronized Statement Block
The synchronized block can be used when a whole method does not need to be synchronized or when you want the thread to get an object-level lock on a different object. The synchronized statement block looks like this:
synchronized (obj) {
    // block of code
}
where obj is a reference to the object whose object-level lock must be acquired before entering the block of code.
This setPoint() method
public synchronized void setPoint(int x, int y) {
    this.x = x;
    this.y = y;
}
can be rewritten to instead use a synchronized block:
public void setPoint(int x, int y) {
    synchronized (this) {
        this.x = x;
        this.y = y;
    }
}
The behavior of both versions of setPoint() is virtually the same. They do compile to different byte-code , but both of them make sure that they have exclusive access to the object-level lock for the instance before making changes to x and y .
Reducing the Time That the Lock Is Held
A synchronized block can be used to reduce the time that the object-level lock is held. If a method does a lot of other things that dont require access to the member variables, it can shorten the time that it holds the lock to just the critical portion:
public void setValues(int x, double ratio) {
    // Some other, long-running statements that dont work
    // with the member variables go here.
    // ...
    double processedValA = // ... long calculation ...
    double processedValB = // ... long calculation ...
    // ...
    synchronized (this) {
        a = processedValA;
        b = processedValB;
    }
}
In setValues() , exclusive access to the object-level lock is not needed until the time-consuming calculations have been made and the results are ready to be stored. At the bottom of the method, the object-level lock is acquired and held briefly to simply assign new values to the member variables a and b .
Locking an Object Other Than this
When a synchronized statement is used as follows ,
synchronized (mutex) {
    // block of code
}
the reference mutex indicates the object whose object-level lock must be acquired before entering the statement block. It can be a reference to any object in the VM, not just this . Regardless of how a thread leaves a synchronized block, it automatically releases the lock. This includes a return statement, a throw statement, or just falling through to the next statement after the block. Calling a method from within the synchronized block does not constitute leaving the block (the lock is still held).
Sometimes you will need to call two synchronized methods on an object and be sure that no other thread sneaks in between the calls. Consider this code fragment from a class called Bucket :
public class Bucket extends Object {
    // ...
    public synchronized boolean isSpaceAvailable() { // ...
    public synchronized void add(BucketItem o)
                         throws NoSpaceAvailableException { // ...
    public synchronized BucketItem remove() { // ...
    // ...
}
All three methods use the synchronized modifier to control concurrent access by multiple threads. Items of the type BucketItem can be added and removed from Bucket . When using the object, a call should be made to isSpaceAvailable() before trying to add another BucketItem , to prevent the NoSpaceAvailableException from being thrown. Of course, there are other ways to design Bucket , but imagine that you are stuck with this class as is. To add an item to Bucket , the following code fragment could be used:
Bucket b = // ...
// ...
if (b.isSpaceAvailable()) {
    b.add(item);
}
This is fine if only one thread is interacting with this instance of Bucket . But if multiple threads are potentially trying to add BucketItem objects to the same Bucket , a new approach has to be taken to avoid a race condition. Imagine that threadA checks and sees that space is available, but before it actually adds its item, threadB checks and also sees that space is available. Now threadA and threadB are racing to actually add an item. Only one can win the race, and that thread gets to add its item. The other thread will fail to add its item and will throw a NoSpaceAvailableException . To prevent this problem, a synchronized block should be wrapped around the two method calls:
Bucket b = // ...
// ...
synchronized (b) {
    if (b.isSpaceAvailable()) {
        b.add(item);
    }
}
The synchronized block uses the object-level lock on b , the Bucket instance. This is the same lock that must be acquired before entering the isSpaceAvailable() and add() methods. If a thread can get the object-level lock and enter the synchronized block, it is guaranteed to be able to invoke isSpaceAvailable() and add() without blocking. Because it already has the object-level lock for b , there is no delay or competition to enter the synchronized methods. In addition, no other thread can invoke these methods until the first thread leaves the synchronized block.
Imagine that threadA is able to get exclusive access to the object-level lock on b and enters the synchronized block. It checks to see if space is available, but before it actually adds its item, the thread scheduler swaps it out. threadB comes along and is blocked from acquiring the lock on b , so it is swapped out. Regardless of any particular thread scheduling, no other thread will be able to get the object-level lock on b until threadA releases it. When threadA is finally scheduled to run again, it will proceed to add its element to b and release the lock as it leaves the synchronized block. After threadA leaves the synchronized block, threadB can acquire the object-level lock on b .
Safely Copying the Contents of a Vector into an Array
The Vector class is used to hold an expandable array of objects. Some of its methods are shown following:
public final synchronized void addElement(Object o)
public final synchronized void insertElementAt(Object o, int pos)
public final synchronized void setElementAt(Object o, int pos)
public final synchronized void removeElementAt(int pos)
public final synchronized boolean removeElement(Object o)
public final synchronized void removeAllElements()
public final int size ()
public final synchronized Object elementAt(int pos)
public final synchronized Object firstElement()
public final synchronized Object lastElement()
public final synchronized Enumeration elements()
public final boolean contains(Object obj)
public final int indexOf(Object obj)
I generated this information using reflection on the Vector class in JDK 1.1 (information on 1.2 and Collections is included later in this chapter). Notice that many of the methods are synchronized particularly the ones used to add and remove elements from the Vector .
SafeVectorCopy in Listing 7.18 shows how a synchronized block can be used in conjunction with the synchronized methods of Vector to copy the current contents of the Vector into a String[] .
Listing 7.18  SafeVectorCopy.javaSafely Copying the Contents of a Vector into an Array
1: import java.util.*;
2:
3: public class SafeVectorCopy extends Object {
4:     public static void main(String[] args) {
5:         Vector vect = new Vector();
6:         vect.addElement(Synchronization);
7:         vect.addElement(is);
8:         vect.addElement(important);
9:
10:         String[] word;
11:
12:         synchronized (vect) {
13:             int size = vect.size();
14:             word = new String[size];
15:
16:             for (int i = 0; i < word.length; i++) {
17:                 word[i] = (String) vect.elementAt(i);
18:             }
19:         }
20:
21:         System.out.println(word.length= + word.length);
22:         for (int i = 0; i < word.length; i++) {
23:             System.out.println(word[ + i + ]= + word[i]);
24:         }
25:     }
26: }
In SafeVectorCopy , three strings are added to a new Vector referred to by vect (lines 58). The synchronized block (lines 1219) uses the object-level lock of vect to keep out other threads while the copying is taking place. Inside the block, the current number of elements in the Vector is used to allocate a new String[] (lines 1314). One by one, each element is retrieved, cast into a String , and stored into the array (lines 1618). If another thread had been trying to access vect to add or remove elements during this time, it would have been blocked until the thread doing the copying left the synchronized block.
  Caution As of JDK 1.2, Vector and Hashtable have been incorporated into the Collections API. All the old methods are still present, but some new ones have been added that are not synchronized . The SafeVectorCopy example is only really safe for pre-1.2 code. Later in this chapter, Ill show you how to safely use Collections in a multithreaded environment.

Toc


Java Thread Programming
Java Thread Programming
ISBN: 0672315858
EAN: 2147483647
Year: 2005
Pages: 149
Authors: Paul Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net