Coordinating Thread Activities Threading Concepts

Suspended A thread is suspended if it cannot simply start processing. That is, it isnt running and wouldnt be able to use the processor even if it were given some CPU time. But it will, at some point in the future, run once again. Dead A thread is dead if it has no further operations to perform. It is very hard, inside a program, to distinguish between active and inactive threads. The problem is this: suppose you had some way of determining whether a thread was active. By the time you got around to taking action based on this information, the answer might very well have changed. From the point of view of thread management, the important distinction is between running and suspended. The three operations you need most are: • Suspend a thread for a period of time and then have it resume running. This is frequently useful when applications are polling a source of information. For example, an email client will occasionally check to see whether more email has arrived. It doesnt need to do so very often, so suspending the thread that checks for new email can be a useful thing to do. • Suspend a thread until a particular mutex becomes available, and then have it grab the mutex and resume running. This is exactly what we want for our bank example. • Suspend a thread until another thread has died. This is often used for reporting back on the outcome of a task. For example, generating a monthly report on a database might involve two threads: one that actually does the computation and another that waits for that thread to finish so it can tell the world that the monthly report is available.

11.3.2 Coordinating Thread Activities

An operation is sometimes called an atomic operation if it cant be interrupted. That is, if a thread is active and performing the operation, it cannot be deactivated until the operation is completed. In a single-processor machine, this also means that no other thread can become active until the operation is completed. The Java programming language provides a small set of atomic operations. Many of the atomic operations deal with things such as variable assignments. For example, a thread executing the following line of code cannot be deactivated until the assignment is complete: int i = 7; You can see why this needs to be atomic; if a thread begins to set a variable, it should finish doing so before relinquishing the processor. The other important atomic operations involve mutexes. Getting or releasing a mutex is guaranteed to be atomic. People new to threading often think that most problems can be solved if we make other, larger operations atomic. To see why this isnt the case, consider the case of Mary and Rachel performing multiple withdrawals. If we had an atomic keyword, the code might be: public atomic void makeWithdrawalMoney amount throws RemoteException, OverdraftException, NegativeAmountException { checkForNegativeAmountamount; checkForOverdraftamount; _balance.subtractamount; calls the method below. return; } This only solves the problem while the code is running on a single-processor machine. As soon as the server moves to a multiple-processor computer, the problem returns. Marys request is handled in one processor, and Rachels is handled in another. Neither thread gets deactivated and too much money is withdrawn. Moreover, making operations atomic has a fairly strong practical limitation™if a thread cannot be deactivated, then nothing else can run on a processor. This means that if an operation can block, or might take a long time, it should not be atomic. The key point is that atomic operations lock a processor and guarantee that the processor will do nothing else until the operation completes. But what we really need is a way to lock a piece of program state and guarantee that no other thread will be able to modify that piece of program state. Traditionally, such locks are created through the use of mutex variables. Mutex variables allow a thread to prevent other threads from interfering with it. Suppose the withdrawal code in our bank example had the following logic: 1. Get a mutex associated to the account. If we cant, wait until we can. 2. Make the withdrawal. 3. Release the mutex. Now, we know that at most one thread will be performing a withdrawal on any given bank account at a time because the withdrawing thread will lock the other threads out. If we add similar logic to the other operations, we guarantee that only one thread is performing an operation on a specific account at any given time. Lets examine each of these in more detail.

11.3.3 Cache Management