Java Reference
In-Depth Information
The sequence of events is as follows:
thread1 starts first and synchronizes on theObject . This prevents any methods for theObject
being called by any other thread.
thread1 then calls sleep() so thread2 can start.
thread2 starts and synchronizes on theOtherObject . This prevents any methods for theOther-
Object being called by any other thread.
thread2 then calls sleep() , allowing thread1 another go.
thread1 wakes up and tries to call method2() for theOtherObject , but it can't until the code
block in thread2 that is synchronized on theOtherObject completes execution.
thread2 gets another go because thread1 can't proceed and tries to call method1() for theOb-
ject . This can't proceed until the code block in thread1 that is synchronized on theObject com-
pletes execution.
Neither thread has any possibility of continuing — they are deadlocked. Finding and fixing this sort of
problem can be very difficult, particularly if your program is complicated and has other threads that continue
to execute.
You can create a trivial deadlock in the last example by making the for loop in main() synchronized on
one of the accounts. For example:
synchronized(accounts[1]) {
for(int i = 1 ; i <= transactionCount ; ++i) {
// code for generating transactions etc...
}
}
A deadlock occurs as soon as a transaction for accounts[1] arises because the doTransaction() meth-
od in the theBank object that is called by a Clerk object to handle the transaction is synchronized to the
same object and can't execute until the loop ends. Of course, the loop can't continue until the method in the
theBank object terminates, so the program hangs.
In general, ensuring that your program has no potential deadlocks is extremely difficult. If you intend to
do a significant amount of programming using threads, you need to study the subject in much more depth
than I can deal with here.
USING EXECUTORS
An executor is an object that you can use to start and manage threads. This can make thread programming
much easier and more efficient. An executor can execute a Runnable task, which is the kind of task that is
already familiar to you. It can also execute a Callable<V> task, which is a task that can execute in a separate
thread, just like a Runnable task, but which also returns a value on completion. The type parameter, V , for
the interface is the type of the value to be returned by the class, so a Callable<> class type must implement
the interface with the appropriate type specified.
The Callable<> interface specifies just one method, call() , that a Callable<> object must implement.
The call() method is the equivalent of the run() method in the Runnable interface, but in addition returns
a value when the method terminates. Thus you can define a task that you want to execute as a separate thread
either by a class that implements the Runnable interface, or by a class that implements the Callable<> in-
terface when you want the task to return a value.
Search WWH ::




Custom Search