|
|
< Day Day Up > |
|
3.6 Choosing a Locking MechanismIf we compare our first implementation of the ScoreLabel class (using synchronized methods) to our second (using an explicit lock), it's easy to conclude that using the explicit lock is not as easy as using the synchronized keyword. With the keyword, we didn't need to create the lock object, we didn't need to call the lock object to grab and release the lock, and we didn't need to worry about exceptions (therefore, we didn't need the try/finally clause). So, which technique should you use? That is up to you as a developer. It is possible to use explicit locking for everything. It is possible to code to just use the synchronized keyword. And it is possible to use a combination of both. For more complex thread programming, however, relying solely on the synchronized keyword becomes very difficult, as we will see. How are the lock classes related to static methods? For static methods, the explicit locks are actually simpler to understand than the synchronized keyword. Lock objects are independent of the objects (and consequently, methods) that use them. As far as lock objects are concerned, it doesn't matter if the method being executed is static or not. As long as the method has a reference to the lock object, it can acquire the lock. For complex synchronization that involves both static and nonstatic methods, it may be easier to use a lock object instead of the synchronized keyword. Synchronizing entire methods is the simplest technique, but as we have already mentioned, it is possible that doing so creates a lock whose scope is too large. This can cause many problems, including creating a deadlock situation (which we examine later in this chapter). It may also be inefficient to hold a lock for the section of code where it is not actually needed. Using the synchronized block mechanism may also be a problem if too many objects are involved. As we shall see, it is also possible to have a deadlock condition if we require too many locks to be acquired. There is also a slight overhead in grabbing and releasing the lock, so it may be inefficient to free a lock just to grab it again a few lines of code later. Synchronized blocks also cannot establish a lock scope that spans multiple methods. In the end, which technique to use is often a matter of personal preference. In this book, we use both techniques. We tend to favor using explicit locks in the later sections of this book, mainly because we use functionality that the Lock interface provides. 3.6.1 The Lock InterfaceLet's look a little deeper into the Lock interface: public interface Lock {
void lock( );
void lockInterruptibly( ) throws InterruptedException;
boolean tryLock( );
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;
void unlock( );
Condition newCondition( );
}What if we want to do other tasks if we can't obtain the lock? The Lock interface provides an option to try to obtain the lock: the tryLock( ) method. It is similar to the lock() method in that if it is successful, it grabs the lock. Unlike the lock() method, if the lock is not available, it does not wait. Instead, it returns with a boolean value of false. If the lock is obtained, the return value is a boolean value of true. By inspecting the return value, we can route the thread to separate tasks: if the value returned is false, for instance, we can route the thread to perform alternative tasks that do not require obtaining the lock. What if we want to wait only for a specific period of time for a lock? The tryLock() method is overloaded with a version that lets you specify the maximum time to wait. This method takes two parameters: one that specifies the number of time units and a TimeUnit object that specifies how the first parameter should be interpreted. For example, to specify 50 milliseconds, the long value is set to 50 and the TimeUnit value is set to TimeUnit.MILLISECONDS. New in J2SE 5.0, the TimeUnit class specifies time in units that are easier to understand. In previous versions of Java, most time-based functionality is either specified in nanoseconds or milliseconds (depending on the method). This method is similar to the lock() method in that it waits for the lock, but only for a specified amount of time. It is similar to the tryLock() method in that it may return without acquiring the lock: it returns with a value of true if the lock is acquired and false if not. What are the other methods of the Lock interface used for? We address them later in this book, starting in Chapter 4. For now, we can already see that the functionality offered by the Lock interface exceeds the functionality offered by the synchronized keyword. By using explicit locks, the developer is free to address issues specific to his program instead of being swamped with concurrency issues. |
|
|
< Day Day Up > |
|