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

3.4 Explicit Locking - Java Threads, Third Edition

Previous Section  < Day Day Up >  Next Section

3.4 Explicit Locking

The purpose of the synchronized keyword is to provide the ability to allow serialized entrance to synchronized methods in an object. Although almost all the needs of data protection can be accomplished with this keyword, it is too primitive when the need for complex synchronization arises. More complex cases can be handled by using classes that achieve similar functionality as the synchronized keyword. These classes are available beginning in J2SE 5.0, but alternatives for use with earlier versions of Java are shown in Appendix A.

The synchronization tools in J2SE 5.0 implement a common interface: the Lock interface. For now, the two methods of this interface that are important to us are lock( ) and unlock(). Using the Lock interface is similar to using the synchronized keyword: we call the lock() method at the start of the method and call the unlock() method at the end of the method, and we've effectively synchronized the method.

The lock() method grabs the lock. The difference is that the lock can now be more easily envisioned: we now have an actual object that represents the lock. This object can be stored, passed around, and even discarded. As before, if another thread owns the lock, a thread that attempts to acquire the lock waits until the other thread calls the unlock() method of the lock. Once that happens, the waiting thread grabs the lock and returns from the lock( ) method. If another thread then wants the lock, it has to wait until the current thread calls the unlock() method. Let's implement our scoring example using this new tool:

package javathreads.examples.ch03.example2;

...

import java.util.concurrent.*;

import java.util.concurrent.locks.*;



public class ScoreLabel extends JLabel implements CharacterListener {

    ...

    private Lock scoreLock = new ReentrantLock( );

    ...

    public void resetGenerator(CharacterSource newGenerator) {

        try {

            scoreLock.lock( );

            if (generator != null)

                generator.removeCharacterListener(this);



            generator = newGenerator;

            if (generator != null)

                generator.addCharacterListener(this);

        } finally {

            scoreLock.unlock( );

        }

    }



    public void resetTypist(CharacterSource newTypist) {

        try {

            scoreLock.lock( );

            if (typist != null)

                typist.removeCharacterListener(this);



            typist = newTypist;

            if (typist != null)

                typist.addCharacterListener(this);

        } finally {

            scoreLock.unlock( );

        }

    }



    public void resetScore( ) {

        try {

            scoreLock.lock( );

            score = 0;

            char2type = -1;

            setScore( );

        } finally {

            scoreLock.unlock( );

        }

    }



    private void setScore( ) {

        // This method will be explained later in chapter 7

        SwingUtilities.invokeLater(new Runnable( ) {

            public void run( ) {

                setText(Integer.toString(score));

            }

        });

    }



    public void newCharacter(CharacterEvent ce) {

        try {

            scoreLock.lock( );

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

            if (ce.source == generator) {

                if (char2type != -1) {

                    score--;

                    setScore( );

                }

                char2type = ce.character;

            }



            // If character is extraneous: 1-point penalty

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

            else {

                if (char2type != ce.character) {

                    score--;

                } else {

                    score++;

                    char2type = -1;

                }

                setScore( );

            }

        } finally {

            scoreLock.unlock( );

        }

    } 

}

This new version of the ScoreLabel class is very similar to the previous version. The implementation now declares an object that implements the Lock interface: the scoreLock object which we'll now use to synchronize the methods. We instantiate an instance of the ReentrantLock class, a class that implements the Lock interface. Instead of declaring methods as synchronized, those methods now call the lock() method on entry and the unlock() method on exit. Finally, the method bodies are now placed in try/finally clauses to handle possible runtime exceptions. With the synchronized keyword, locks are automatically released when the method exits. Using locks, we need to call the unlock() method: by placing the unlock() method call in a finally clause, we guarantee the method is called when the method exits, even if an unexpected runtime exception is thrown.

In terms of functionality, this example is exactly the same as the previous example. In terms of possible enhancements, there is a difference. The difference is that by using a lock class, we can now utilize other functionality梖unctionality, as we shall see, that can't be accomplished by just using the synchronized keyword.

Using a lock class, we can now grab and release a lock whenever desired. We can test conditions before grabbing or releasing the lock. And since the lock is no longer attached to the object whose method is being called, it is now possible for two objects to share the same lock. It is also possible for one object to have multiple locks. Locks can be attached to data, groups of data, or anything else, instead of just the objects that contain the executing methods.

    Previous Section  < Day Day Up >  Next Section