站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Java Threads, Third Edition

3.7 Nested Locks - Java Threads, Third Edition

Previous Section  < Day Day Up >  Next Section

3.7 Nested Locks

Our implementation of the newCharacter() method could be refactored into multiple methods. This isolates the generator and typist logic into separate methods, making the code easier to maintain.

package javathreads.examples.ch03.example5;

    ...

    private synchronized void newGeneratorCharacter(int c) {

        if (char2type != -1) {

            score--;

            setScore( );

        }

        char2type = c;

    }



    private synchronized void newTypistCharacter(int c) {

        if (char2type != c) {

            score--;

        } else {

            score++;

            char2type = -1;

        }

        setScore( );

    }



    public synchronized void newCharacter(CharacterEvent ce) {

        // Previous character not typed correctly: 1-point penalty

        if (ce.source == generator) {

           newGeneratorCharacter(ce.character);

        }



        // If character is extraneous: 1-point penalty

        // If character does not match: 1-point penalty

        else {

           newTypistCharacter(ce.character);

        }

    } 

}

The two new methods (newGeneratorCharacter() and newTypistCharacter()) are synchronized because they access the shared state of the object. However, in this case, synchronizing the methods is not technically necessary. Unlike the other methods that access the shared data, these methods are private; they can be called only from other methods of the class. Within the class, they are called only from synchronized methods. So, there is no reason for these methods to acquire the lock because all calls to the method already own the lock. Yet it's still a good idea to synchronize methods like this. Developers who modify this class may not realize that their new code needs to obtain the object lock before calling one of these new methods.

The reason this works is that Java does not blindly grab the lock when it enters synchronized code. If the current thread owns the lock, there is no reason to wait for the lock to be freed or even to grab the lock. Instead, the code in the synchronized section just executes. Furthermore, the system is smart enough to not free the lock if it did not initially grab it upon entering the synchronized section of code. This works because the system keeps track of the number of recursive acquisitions of the lock, finally freeing the lock upon exiting the first method (or block) that acquired the lock. This functionality is called nested locking.

Nested locks are also supported by the ReentrantLock class梩he class that implements the Lock interface that we have been using so far. If a lock request is made by the thread that currently owns the lock, the ReentrantLock object just increments an internal count of the number of nested lock requests. Calls to the unlock() method decrement the count. The lock is not freed until the lock count reaches zero. This implementation allows these locks to behave exactly like the synchronized keyword. Note, however, that this is a specific property of the ReentrantLock class and not a general property of classes that implement the Lock interface.

Whyis Java's support of nested locks important? This was a simple example. A more complex梐nd very common梕xample is that of cross-calling methods. It is possible for a method of one class to call methods of another class, which in turn may call methods of the original class. If Java did not support nested locks梐nd the methods of both classes were synchronized梬e could deadlock the program.

The deadlock occurs because the final method tries to grab a lock that the current thread has already grabbed. This lock can't be freed until the original method unlocks it, but it can't unlock it until it completes the execution of the original method. And the original method can't complete its execution because the final method does not return: it is still waiting to grab the lock.

Cross-calling methods are common and can be so complex that it may not be possible to even detect them, making fixing potential deadlocks very difficult. And there are more complex cases as well. Our example uses a callback mechanism by using character sources and listeners. In this case, character sources and listeners are connected independently of either class: it can become very complex if the listeners are being changed constantly during operation.

Cross-calling methods and callbacks are very prevalent in Java's core library梡articularly the windowing system, with its dependency on event handlers and listeners. Developing threaded applications梠r even just using Java's standard classes梬ould be very difficult if nested locks were not supported.

Is it possible to detect how many times a lock has been recursively acquired? It is not possible to tell with the synchronized keyword, and the Lock interface does not provide a means to detect the number of nested acquisitions. However, that functionality is implemented by the ReentrantLock class:

public class ReentrantLock implements Lock {

    public int getHoldCount( );

    public boolean isLocked( );

    public boolean isHeldByCurrentThread( );

    public int getQueueLength( );

}

The getHoldCount() method returns the number of acquisitions that the current thread has made on the lock. A return value of zero means that the current thread does not own the lock: it does not mean that the lock is free. To determine if the lock is free梟ot acquired by any thread梩he isLocked() method may be used.

Two other methods of the ReentrantLock class are also important to this discussion. The isHeldByCurrentThread() method is used to determine if the thread is owned by the current thread, and the getQueueLength() method can be used to get an estimate of the number of threads waiting to acquire the lock. This value that is returned is only an estimate due to the race condition that exists between the time that the value is calculated and the time that the value is used after it has been returned.

    Previous Section  < Day Day Up >  Next Section