Level: Advanced Allen Holub (threading@holub.com), Freelance author
10 Oct 2000 Allen Holub suggests that the Java programming language's threading model is possibly the weakest part of the language. It's entirely inadequate for programs of realistic complexity and isn't in the least bit object oriented. This article proposes significant changes and additions to the Java language that would address many of these problems. The Java threading model is one of the least satisfactory aspects of the language. Though it's a good thing that threading is built in, the syntactic and package-level support for threads is so minimal as to be worthless for all but the most trivial applications. Most books on Java threading present a litany of everything wrong with Java threading model and a class library of Band-Aid solutions to those problems. I call the classes Band-Aids because the problems addressed by the classes should really be part of the syntax of the Java language. Using syntactic methods rather than libraries can give you better code in the long run since the compiler and JVM, working together, can perform optimizations that would be difficult or impossible with a library approach. In my book Taming Java Threads (see Resources), and in this article, I go one step further and suggest a more positive approach to the threading problem by suggesting a few changes
to the Java programming language that would provide real solutions. The main difference between this article and the book is that I've thought about things a bit more, so have improved the proposals a bit. These proposals are very tentative -- they are just one person's thoughts on the matter, and they would need a lot of work and peer review before being viable. But they're a start. I intend to set up a working group on these issues; if you're interested, send e-mail to threading@holub.com and I'll drop you a note when something real starts happening. The proposals presented here are also rather bold. Several people have suggested subtle, and minimal,
changes to the Java Language Specification (JLS) (see Resources) to fix currently ambiguous JVM behavior, but I want more sweeping improvement. On a practical note, many of my proposals involve the introduction of new keywords to the language. Though the usual requirement that you don't want to break existing code is certainly valid, if the language is not to stagnate and thus become obsolete, it must be possible to introduce keywords. In order to introduce keywords that won't conflict with existing identifiers, I've deliberately used a character ($) that is illegal in an identifier. (For example, $task rather than task). A compiler command-line switch could perhaps enable variants on these keywords that would omit the dollar sign. The concept of task
The fundamental problem with the Java threading model is the fact that it is not in the least bit object oriented. OO designers don't think in terms of threads at all; rather, they think of synchronous messages (which are processed immediately -- the message handler doesn't return until the message is complete) and asynchronous messages (which are processed in the background some time after
the message is received -- the message handler returns immediately, long before the message is processed). The Java programming language's Toolkit.getImage() method is a good example of an asynchronous message. The getImage() message handler returns immediately, before the entire image is fetched by a background thread. That's the OO approach, but as I said earlier, the Java threading model isn't OO.
A Java programming language thread is effectively nothing but a procedure (run()) that calls other procedures. Notions of objects, asynchronous versus synchronous messages, and the like, are simply not addressed. One solution to this problem, discussed in depth in my book, is an Active_object. Active Objects are objects that can receive asynchronous requests, which are processed in the background some time after the request is received. In the Java programming language, a request can be encapsulated in an object -- for example, you can pass the active object an instance of a Runnable implementation whose run() method encapsulates the work to do. This runnable object is queued up by the active object and then executed on a background thread when the object gets around to it. The asynchronous messages running on an active object are effectively synchronous with respect to each
other since they're dequeued and executed one at a time by a single service thread. Consequently, you can
eliminate much of the synchronization hassles required to program in a more procedural model by using
an active object. In a way, the Java programming language's entire Swing/AWT subsystem is an active object. The only safe way to send a message to a Swing class is to call a method like SwingUtilities.invokeLater(), which posts a runnable object on the Swing event queue to be handled by the Swing event-processing thread when it gets around to it. My first proposal, then, is to incorporate active objects into the language itself by incorporating the notion of a task into the Java programming language. (The notion of a task is borrowed from Intel's RMX operating system and the Ada programming language. Most real-time operating systems support similar concepts.) A task has a built-in active-object dispatcher, and takes care of all the mechanics of handling
asynchronous messages automatically. You define a task exactly as you would a class, except that the asynchronous modifier
could be applied to methods of the task to indicate that those methods should execute in the background on the active-object dispatcher. To see the parallels with the class-based approach discussed in Chapter 9 of my book, consider the following file I/O class, which uses the Active_object class discussed in Taming Java Threads to implement an asynchronous write operation:
interface Exception_handler
{ void handle_exception( Throwable e );
}
class File_io_task
{ Active_object dispatcher = new Active_object();
final OutputStream file;
final Exception_handler handler;
File_io_task( String file_name, Exception_handler handler )
throws IOException
{ file = new FileOutputStream( file_name );
this.handler = handler;
}
public void write( final byte[] bytes )
{
// The following call asks the active-object dispatcher
// to enqueue the Runnable object on its request
// queue. A thread associated with the active object
// dequeues the runnable objects and executes them
// one at a time.
dispatcher.dispatch
( new Runnable()
{ public void run()
{
try
{ byte[] copy new byte[ bytes.length ];
System.arrayCopy( bytes, 0,
copy, 0,
bytes.length );
file.write( copy );
}
catch( Throwable problem )
{ handler.handle_exception( problem );
}
}
}
);
}
}
|
All write requests are queued up on the active-object's input queue with a dispatch() call. Any exceptions that occur while processing the asynchronous message in the background are handled by the
Exception_handler object that's passed into the File_io_task's constructor.
You would write to the file like this:
The main problem with this class-based approach is that it's way too complicated -- adding way too much clutter to the code to do a simple thing. Introducing the $task and $asynchronous keywords to the language lets you rewrite the previous code as follows:
$task File_io $error{ $.printStackTrace(); }
{
OutputStream file;
File_io( String file_name ) throws IOException
{ file = new FileOutputStream( file_name );
}
asynchronous public write( byte[] bytes )
{ file.write( bytes );
}
} |
Note that asynchronous methods don't specify return values because the handler returns immediately, long before the requested operation completes. Consequently, there is not reasonable value that could be returned. The $task keyword should work exactly like class with respect to the
derivation model: a $task could implement interfaces, extend classes, and extend other tasks.
Methods marked with the asynchronous keyword are handled by the $task in the background. Other methods would work synchronously, just as they do in classes. The $task keyword can be modified with an optional $error clause (as shown),
which specifies a default handler for any exceptions that are not caught by the asynchronous methods themselves. I've used $ to represent the thrown exception object. If no $error clause is specified, then a reasonable error message (and probably stack trace) is printed. Note that the arguments to the asynchronous method must be immutable to be thread safe. The run-time system should take care of any semantics required to guarantee this immutability. (A simple copy is often
not sufficient.) All task objects would have to support a few pseudo-messages as well: some_task.close() | Any asynchronous messages sent after this call is issued would throw a TaskClosedException. Messages waiting on the active-object queue will be serviced, however. | some_task.join() | The caller blocks until the task is closed and all pending requests are processed. |
In addition to the usual modifiers (public, etc.), the task keyword would accept the $pooled(n) modifier, which would cause the task to use a thread pool rather than a single thread to run the asynchronous requests. The n specifies the desired
pool size; the pool can grow if necessary, but should deflate to the original size when the additional threads aren't required. The pseudo-field $pool_size returns the original n argument to the
$pooled(n) directive. I present a server-side socket handler as an example of thread-pool usage in Chapter 8 of Taming Java Threads, an implementation of which is a good example of a pooled task. The basic idea is to create a single object whose job is to monitor a server-side socket. Every time a client connects, the socket-server object would grab a thread from a pool of dormant pre-created threads and set that thread to work servicing the client connection. If more clients tried to connect than there were pre-created threads, the socket server would create additional client-service threads, but these extra threads would be discarded once the connection closed. You could implement a socket server in the proposed syntax as follows:
The Socket_server object uses a single background thread to handle the asynchronous listen() request, which encapsulates the socket "accept" loop. As each client connects, listen() asks a Client_handler to handle the request by calling handle(). Each handle() request executes on its own thread (because this is a
$pooled task). Note that every asynchronous message sent to a $pooled $task is effectively handled on its own thread. Since a $pooled $task is typically used to implement an autonomous operation, probably the best way to handle the potentially hideous synchronization problems associated with the access of state variables is for the this reference in the $asynchronous method to reference a unique copy of the object. That is, when you send an asynchronous request to a $pooled $task, a clone() operation is performed and the method's this reference will reference the clone. Communication between threads can be done by means of synchronized access to static fields.
Improvements to synchronized
Though a $task eliminates the need for synchronization in many situations, all multithreaded systems cannot be implemented solely in terms of tasks. Consequently, the existing threading model needs to be updated as well. The synchronized keyword has several flaws:
- You cannot specify a timeout value.
- You cannot interrupt a thread that is waiting to acquire a lock.
- You cannot safely acquire multiple locks. (Multiple locks should always be acquired in the same order.)
You can solve these problems by extending the syntax of synchronized both to support a list of multiple arguments and to accept a timeout specification (specified in brackets, below). Here's the syntax that I'd like: synchronized(x && y && z) | Acquire the locks on the x, y, and z objects. | synchronized(x || y || z) | Acquire the locks on the x, y, or z objects. | synchronized( (x && y ) || z) | Obvious extension of the previous code. | synchronized(...)[1000] | Acquire a single lock with a one-second timeout. | synchronized[1000] f(){...} | Acquire the lock for this on entering f(), but with a one-second timeout. |
A TimeoutException is a RuntimeException derivative that is thrown if the wait times out. A timeout is necessary, but not sufficient, for making the code robust. You also need to be able to terminate the wait to acquire the lock externally. Consequently, the interrupt() method, when sent to a thread that is waiting to acquire a lock, should break the waiting thread out of the acquisition block by tossing a SynchronizationException object. This exception should be a RuntimeException derivative so that it would not have to be handled explicitly. The main problem with these proposed modifications to the synchronized syntax is that
they would require changes at the bytecode level, which currently implements synchronized
using enter-monitor and exit-monitor instructions. These instructions take no arguments, so the bytecode definition would have to be extended to support the acquisition of multiple locks. This change is no more serious than the changes added to the JVM in Java 2, however, and would be backward compatible with existing Java code. Another solvable problem is the most common deadlock scenario, where two threads are both waiting for each other to do something. Consider the following, admittedly contrived, example:
class Broken
{ Object lock1 = new Object();
Object lock2 = new Object();
void a()
{ synchronized( lock1 )
{ synchronized( lock2 )
{ // do something
}
}
}
void b()
{ synchronized( lock2 )
{ synchronized( lock1 )
{ // do something
}
}
} |
Imagine that one thread calls a(), but is preempted after acquiring lock1 but before acquiring lock2. A second thread comes along and calls b(), acquires lock2, but can't get lock1 because the first thread has it. The first thread now wakes up and tries to acquire lock2, but can't get it because thread two has it. Deadlock.
The synchronize-on-multiple-objects syntax can solve this problem as follows:
//...
void a()
{ synchronized( lock1 && lock2 )
{
}
}
void b()
{ synchronized( lock2 && lock3 )
{
}
}
|
The compiler (or VM) will rearrange the acquisition order so that lock1 is always acquired
first, thereby eliminating the deadlock. But, it's not always possible to use multiple acquisition, so it would be nice to provide some way to break the deadlock automatically. One simple strategy is to release the lock occasionally if you've acquired one lock and are waiting for a second. That is, instead of
waiting forever, wait like this:
while( true )
{ try
{ synchronized( some_lock )[10]
{ // do the work here.
break;
}
}
catch( TimeoutException e )
{ continue;
}
} |
If everybody who's waiting for the lock uses a slightly different timeout value, the deadlock will be broken and one of the threads will get to run. I propose the following syntax to represent the preceding code:
synchronized( some_lock )[]
{ // do the work here.
} |
The synchronized statement waits forever, but gives up the lock occasionally to break a potential deadlock. Ideally, the timeout used for each iteration would differ from the previous iteration by some random amount.
Improvements to wait() and notify()
The wait()/notify() system also has problems:
- There is no way to detect whether
wait() has returned normally or because of a timeout.
- There is no way to implement a traditional condition variable that remains in a "signaled" state.
- Nested-monitor lockout can happen too easily.
The timeout-detection problem is easily solved by redefining wait() to return a boolean (rather than void). A true return value would indicate a normal return, false would indicate a timeout. The notion of a state-based condition variable is an important one. The variable can be set to a false state in which waiting threads will block until the variable enters a true state; any thread that waits on a true condition variable is released immediately. (The wait() call won't block in this case.) You can support this functionality by extending the syntax of notify() as follows: | notify(); | Release all waiting threads without changing the state of the underlying condition variable. | | notify(true); | Set the condition variable's state to true and release any waiting threads. Subsequent calls to wait() won't block. | | notify(false); | Set the condition variable's state to false (subsequent calls to wait() will block). |
The nested-monitor-lockout problem is thornier, and I don't have an easy solution. Nested-monitor lockout is a form of deadlock that occurs when the holder of some lock doesn't release it before suspending itself. Here's an (again, contrived) example that demonstrates the problem, but real-world examples are legion:
class Stack
{
LinkedList list = new LinkedList();
public synchronized void push(Object x)
{ synchronized(list)
{ list.addLast( x );
notify();
}
}
public synchronized Object pop()
{ synchronized(list)
{ if( list.size() <= 0 )
wait();
return list.removeLast();
}
}
} |
The problem is that two locks are involved in both the get() and put() operations: one on the Stack object and another on the LinkedList. Consider what happens when a thread tries to pop from an empty stack. The thread acquires both locks, then calls wait(), which releases the lock on the Stack object, but not the one on the list. If a second thread
tries to push an object, it will be suspended forever at the synchronized(list) statement and never be permitted to actually push an object. Since a nonempty stack is the condition that the first thread is waiting for, this is a deadlock. That is, the first thread will never return from wait(), because the second thread can never reach the notify(), because the first thread's holding of the lock is preventing the second thread from reaching that code. There are lots of obvious solutions in the current example: do everything with method-level synchronization, for example. In the real world, the solution is not usually so simple, though. One possible solution is for wait() to release all the locks that the current thread has acquired in the opposite order of acquisition and then reacquire them in the original acquisition order when the wait is satisfied. I can imagine that code that leveraged this behavior would be almost impossible for a human being to figure out, however, so I don't think that this is really a viable solution. If anybody has any ideas, send me e-mail. I'd also like to be able to wait for complex conditions to become true. For example:
where a, b, and c are any Object.
Fixing the Thread class
The ability to support both preemptive and cooperative threads is essential in some server applications, especially if you intend to squeeze maximum performance out of the system. I suggest that the Java programming language has gone too far in simplifying the threading model, and that the Posix/Solaris notion of a "green thread" and a "lightweight process" (discussed in Chapter 1 of Taming Java Threads) should be supported by the Java programming language. This means, of course, that some JVM implementations (such as NT implementations) would have to simulate cooperative threads internally, and other JVMs would have to simulate preemptive threading, but adding these extensions to the JVM is reasonably easy to do. A Java Thread, then, should always be preemptive. That is, a Java programming language thread should work much like a Solaris lightweight process. The Runnable interface can be used to define a Solaris-like "green thread" that must explicitly yield control to other green threads running on the same lightweight process. For example, the current syntax of:
would effectively create a green thread for the Runnable object and bind that green thread to the lightweight process represented by the Thread object. This change in implementation is transparent to existing code since the effective behavior is the same as at present. By thinking of Runnable objects as green threads, you can expand the Java programming language's existing syntax to support multiple green threads bound to a single lightweight process simply by passing several Runnable objects to the Thread constructor. (The green threads would be cooperative with respect to each other, but could be preempted by other green threads (Runnable objects) running on other lightweight processes (Thread objects)).
For example, the following code would create a green thread for each of the runnable objects, and these green threads will share the lightweight process represented by the Thread object.
new Thread( new My_runnable_object(), new My_other_runnable_object() );
|
The existing idiom of overriding Thread and implementing run() should still work, but it should map to a single green thread bound to a lightweight process. (The default run() method in the Thread() class would effectively create a second Runnable object internally.)
Inter-thread coordination
More facilities should be added to the language to support inter-thread communication. Right now, the PipedInputStream and PipedOutputStream classes can be used for this purpose, but they are much too inefficient for most applications. I propose the following additions to the Thread class:
- Add a
wait_for_start() method that blocks until a thread's run() method starts up. (It would be okay if the waiting thread was released just before run was called.) This way one thread could create one or more auxiliary threads, and then be assured that the auxiliary threads were running before the creating thread would continue with some operation.
- Add (to the
Object class) $send(Object o) and Object=$receive() methods that would use an internal blocking queue to pass objects between threads. The blocking queue would be created automatically as a side effect of the first $send() call. The $send() call would enqueue the object; the $receive() call would block until an object was enqueued, and then return that object. Variants on these methods would support timeouts on both the enqueue and dequeue operations: $send(Object o, long timeout) and $receive(long timeout);
Internal support for reader-writer locks
The notion of a reader-writer lock should be built into the Java programming language. Reader-writer locks are discussed in Taming Java Threads (and elsewhere), but to summarize: a reader-writer lock
enforces the rule that multiple threads can simultaneously access an object, but only one thread at a time
could modify the object, and modifications cannot go on while accesses are in progress. The syntax for a reader-writer lock can be borrowed from that of the synchronized keyword: static Object global_resource;
//...
public void a()
{
$reading( global_resource )
{ // While in this block, other threads requesting read
// access to global_resource will get it, but threads
// requesting write access will block.
}
}
public void b()
{
$writing( global_resource )
{ // Blocks until all ongoing read or write operations on
// global_resource are complete. No read or write
// operation or global_resource can be initiated while
// within this block.
}
}
public $reading void c()
{ // just like $reading(this)...
}
public $writing void d()
{ // just like $writing(this)...
}
|
Multiple threads would be able to enter $reading blocks only if no thread was in a $writing block for a given object. A thread that tries to enter a $writing block while reads are in progress will block until the reading threads exit the $reading blocks. Threads that try to enter a $reading or $writing block while another thread is in a $writing block will block until the writing thread exits the $writing block. If both reading and writing threads are waiting for access, the reading threads will get access first by default, but this default can be changed by modifying the class definition with the $writer_priority attribute. For example: $write_priority class IO
{
$writing write( byte[] data )
{ //...
}
$reading byte[] read( )
{ //...
}
} |
Access to partially constructed objects should be illegal
The JLS currently permits access to a partially created object. For example, a thread created within a constructor can access the object being constructed, even though that object might not be fully constructed. The behavior of the following code is undefined:
class Broken
{ private long x;
Broken()
{ new Thread()
{ public void run()
{ x = -1;
}
}.start();
x = 0;
}
} |
The thread that sets x to -1 can run in parallel to the thread that sets x to 0. Consequently, the value of x is unpredictable. One possible solution to this problem is to require that the run() methods of threads started from within a constructor not execute until that constructor returns, even if the thread created by the constructor is of higher priority than the one that called new. That is, the start() request must be deferred until the constructor returns.
Alternatively, the Java programming language could permit the synchronization of constructors. In other words, the following code (which is currently illegal) would work as expected: class Illegal
{ private long x;
synchronized Broken()
{ new Thread()
{ public void run()
{ synchronized( Illegal.this )
{
x = -1;
}
}
}.start();
x = 0;
}
}
|
I think that the first approach is cleaner than the second one, but it is admittedly harder to implement.
The volatile keyword should always work as expected
The JLS requires that the ordering of volatile operations be preserved. Most JVM implementations simply ignore this part of the spec and shouldn't. There are hosts of similar problems that come up in multi-processor situations that also need to be addressed in the JLS. If you're interested in this side of the threading mess, Bill Pugh at the University of Maryland is spearheading this effort (see Resources).
Access issues
The lack of good access control makes threading more difficult than necessary. Often, methods don't have to be thread safe if you can guarantee that they are called only from synchronized subsystems. I'd tighten up the Java programming language's notion of access privilege as follows:
- Require explicit use of the
package keyword to specify package access. I think that the very existence of a default behavior is a flaw in any computer language, and am mystified that a default access privilege even exists (and am even more mystified that the default is "package" rather than "private"). The Java programming language doesn't supply the equivalent of a default keyword anywhere else. Even though the requirement for an explicit package specifier would break existing code, it would make that code a lot easier to read and could eliminate whole classes of potential bugs (if the access privilege had been omitted in error rather than deliberately, for example).
- Reintroduce
private protected, which works just like protected does now, but does not permit package access.
- Permit the syntax
private private to specify "implementation access:" private to all outside objects, even objects of the same class as the current object. The only reference permitted to the left of the (implicit or explicit) dot is this.
- Extend the syntax of
public to grant access to specific classes. For example, the following
code would permit objects of class Fred to call some_method(), but the method would be private with respect to all other classes of objects.
public(Fred) void some_method()
{
} |
This proposal is different from the C++ "friend" mechanism, which grants a class full access to all private parts of another class. Here, I'm suggesting a tightly controlled access to a limited set of methods. This way one class could define an interface to another class that would be invisible to the rest of the system. An obvious variant is:
public(Fred, Wilma) void some_method()
{
} |
-
Require all field definitions to be private unless they reference truly immutable objects or define static final primitive types. Directly accessing the fields of a class violates two basic principles of OO design: abstraction and encapsulation. From the threading perspective, allowing direct access to fields just makes it easier to inadvertently have unsynchronized access to a field.
-
Add the $property keyword. Objects tagged in this way are accessible to a "bean box" application that is using the introspection APIs defined in the Class class, but otherwise work identically to private private. The $property attribute should be applicable to both fields and methods so that existing JavaBean getter/setter methods could be easily defined as properties.
 |
Immutability
The notion of immutability (an object whose value cannot change once it's created) is invaluable in multithreaded situations since read-only access to immutable objects doesn't have to be synchronized.
The Java programming language's implementation of immutability isn't tight enough for two reasons:
- It is possible for an immutable object be accessed before it's fully created, and this access might yield an incorrect value for some field.
- The definition of immutable (a class, all of whose fields are
final) is too loose. Objects addressed by final references can indeed change state, even though the reference itself cannot change state.
The first problem is solved by not permitting threads to be started from within constructors (or by delaying the start request until just before the constructor returns). The second problem can be solved by requiring that final references point to immutable objects. That is, an object is really immutable only if all of its fields are final and all of the fields of any referenced objects are final as well. In order not to break existing code, this definition could be enforced by the compiler only when a class is explicitly tagged as immutable as follows:
$immutable public class Fred
{
// all fields in this class must be final, and if the
// field is a reference, all fields in the referenced
// class must be final as well (recursively).
static int x constant = 0; // use of `final` is optional when $immutable
// is present.
}
|
Given the $immutable tag, the use of final in the field definitions could be optional. Finally, a bug in the Java compiler makes it impossible to reliably create immutable objects when inner classes are on the scene. When a class has nontrivial inner classes (as most of mine do), the compiler often incorrectly prints the error message: "Blank final variable 'name' may not have been initialized.
It must be assigned a value in an initializer, or in every constructor." |
This message can appear even though the blank final is indeed initialized in every constructor. This bug has been in the compiler since inner classes were first introduced in version 1.1, and at this writing (three years later) the bug is still there. It's about time that this bug was fixed.
Instance-level access of class-level fields
In addition to access privileges, there is also the problem that both class-level (static)
methods and instance (non-static) methods can directly access class-level (static) fields. This access is dangerous because synchronizing the instance method doesn't grab the class-level lock, so a synchronized static method can access the class field at the same time as a synchronized instance method. The obvious solution to this problem is to require that non-immutable static fields be accessed from instance methods via static accessor methods. This requirement would mandate both compile and run-time checks, of course. Under these guidelines, the following code would be illegal:
class Broken
{
static long x;
synchronized static void f()
{ x = 0;
}
synchronized void g()
{ x = -1;
}
}; |
because f() and g() can run in parallel and modify x simultaneously (with undefined results). Remember, there are two locks here: the static method acquires the lock associated with the Class object and the nonstatic method acquires the lock associated with the instance. The compiler should either require the following structure when accessing non-immutable static fields from instance methods:
class Broken
{
static long x;
synchronized private static accessor( long value )
{ x = value;
}
synchronized static void f()
{ x = 0;
}
synchronized void g()
{ accessor( -1 );
}
}
|
Or the compiler should require the use of a reader/writer lock:
class Broken
{
static long x;
synchronized static void f()
{ $writing(x){ x = 0 };
}
synchronized void g()
{ $writing(x){ x = -1 };
}
}
|
Alternatively -- and this is the ideal solution -- the compiler should automatically
synchronize access to non-immutable static fields with a reader/writer lock so that the programmer
wouldn't have to worry about it.
Abrupt shutdown of daemon threads
Daemon threads are shut down abruptly when all the non-daemon threads terminate. This is a problem when the daemon has created some sort of global resource (such as a database connection or a temporary file), but hasn't closed or destroyed that resource when it is terminated. I'd solve that problem by making the rule that the JVM will not shut down the application if:
- Any non-daemon threads are running, or
- Any daemon threads are executing a
synchronized method or synchronized block of code.
The daemon thread is subject to abrupt termination as soon as it leaves the synchronized block or synchronized method.
Bring back the stop(), suspend(), and resume() methods
This one may not be possible for practical reasons, but I'd like stop() not to be deprecated (in both Thread and ThreadGroup). I would change the semantics so that calling stop() wouldn't break your code, however. The problem with stop(), you'll remember, is that stop() gives up any locks when the thread terminates, thereby potentially leaving the objects that the thread is working on in an unstable (partially modified) state. These objects can nonetheless be accessed since the stopped thread has released its lock on the object. This problem can be solved by redefining the behavior of stop() such that the thread would terminate immediately only if it is holding no locks. If it is holding locks, I'd like the thread to be terminated immediately after releasing the last one. You could implement this behavior with a mechanism similar to an exception toss. The stopped thread would set a flag that would be tested immediately after exiting all synchronized blocks. If the flag was set, an implicit exception would be thrown, but the exception would not be catchable and would not cause any output to be generated when the thread terminated. Note that Microsoft's NT operating system doesn't handle an abrupt externally implied stop very well. (It doesn't notify dynamic-link libraries of the stop, so system-wide resource leaks can develop.) That's why I'm recommending an exception-like approach that simply causes run() to return. The practical problem with this exception-style approach is that you'd have to insert code to test the "stopped" flag at the end of every synchronized block, and this extra code would both slow down
the program and make it larger. Another approach that comes to mind is to make stop() implement a "lazy" stop in which the thread terminates the next time it calls wait() or yield(). I'd also add isStopped() and stopped() methods to Thread (which would work much like isInterrupted() and interrupted(), but would detect the "stop-requested" state). This solution isn't as universal as the first, but
is probably workable and wouldn't have the overhead. The suspend() and resume() methods should just be put back into the Java programming language. They're useful, and I don't like being treated like a kindergartner. Removing them simply because they are potentially dangerous -- a thread can be holding a lock when suspended -- is insulting. Let me decide whether or not I want to use them. Sun could always make it a run-time exception for suspend() to be called if the receiving thread is holding any locks, or better yet, defer the actual suspension until the thread gives up its locks.
Blocking I/O should work correctly
You should be able to interrupt any blocking operation, not just just wait() and sleep(). I discussed this issue in the context of sockets in Chapter 2 of Taming Java Threads, but right now, the only way to interrupt a blocking I/O operation on a socket is to close the socket, and there's no way to interrupt a blocking I/O operation on a file. Once you initiate a read request, for example, the thread is blocked until it actually reads something. Even closing the file handle does not break you out of the read operation. Moreover, your program must be able to timeout of a blocking I/O operation. All objects on which blocking operations can occur (such as InputStream objects) should support a method like this:
InputStream s = ...;
s.set_timeout( 1000 );
|
This is the equivalent to the Socket class's setSoTimeout(time) method. Similarly, you should be able to pass a timeout into the blocking call as an argument.
The ThreadGroup class
ThreadGroup should implement all the methods of Thread that can change a thread's state. I particularly want it to implement join() so that I can wait for all threads in the group to terminate.
Wrapping up
So that's my list. As I said in the title of this article, if I were king... (sigh). I'm hoping that some of these changes (or their equivalent) will eventually migrate into the language. I really think that the Java language is a great programming language; but I also think that the Java threading model simply wasn't thought-out well enough, and that's a pity. The Java programming language is evolving, however, so there is a path to follow to improve the situation.
Resources - This article is an updated excerpt from Allen Holub's
Taming Java Threads
. This book presents a long discussion of the traps and pitfalls of multithreaded programming in Java programs and also presents a Java package of threading-related classes that solve those problems.
- Bill Pugh at the University of Maryland is spearheading the effort to revise the JLS with an improved threading model. Bill's proposal is not as broad as what was presented in this article, primarily focusing on getting the existing threading model to work in a reasonable way. Get more information at The Java Memory Model Web site.
- Find the complete Java Language Specification from the
Sun Web site.
- For a highly technical approach to threading, see
Concurrent Programming in Java: Design Principles and Patterns, 2nd edition
by Doug Lea. This is a great book but it is written in a dense, academic style that will make it inaccessible to some readers. Makes a good supplement to Taming Java Threads.
-
Java Threads
by Scott Oaks and Henry Wong is more lightweight than Taming Java Threads but might be a better choice if you've never programmed threads before. Oaks and Wong implement some of the same helper classes Holub does, and it's always useful to see alternative solutions to similar problems.
- A good introduction (not Java-specific) to threads is
Threads Primer: A Guide to Multithreaded Programming
by Bill Lewis and Daniel J. Berg.
- You can find some technical notes on Java threading at the Sun Web site.
About the author  | |  | Allen Holub has been working in the computer industry since 1979. He is widely published in magazines (Dr. Dobb's Journal, Programmers Journal, Byte, MSJ, among others), and he writes the "Java Toolbox" column for the online magazine
JavaWorld
. Allen has eight books to his credit, the latest of which covers the traps and pitfalls of Java threading (Taming Java Threads). He's been designing and building object-oriented software for longer than he cares to remember. After eight years as a C++ programmer, Allen abandoned C++ for Java in early 1996. He now looks at C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming (first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University of California Berkeley Extension since 1982. Allen offers both public classes and in-house training in Java and object-oriented design topics. He also does object-oriented design consulting and contract Java programming. Get information, and contact Allen, via his Web site www.holub.com.
|
Rate this page
|