Recent top five:
Let's talk about exceptions ...
How do you handle exceptions? Do you think upfront about the type of exceptions that you want to catch or do you just let
the outside world handle it?
-- Jeroen van Bergen in JW Blogs
| Enterprise AJAX - Transcend the Hype |
| Memory Analysis in Eclipse |
| Oracle Compatibility Developer's Guide |
| Memory Analysis in Eclipse |
I would like to know how to write a thread-safe client/server program in Java. How should I approach the threading? What sort
of issues should I be aware of?
Thread-safe programming is only necessary if you have data that can be modified by more than one thread at a time. In a client/server
situation, usually the server has multiple clients. If all those clients do is read data from the server, and if no other
programs are modifying that data, then you have nothing to worry about. But if those clients can change shared data on the
server, they must not be allowed to conflict with one another. Normally, if two clients try to change shared data, you have
to hope that the first client is able to finish with the data before the second client begins to modify it. This situation
is called racing.
Under these circumstances, the outcome depends on how the underlying scheduler happens to allocate time to the various clients' requests. In the case of Java, one doesn't know what the underlying scheduler's policies are (because they're platform-dependent, or natively implemented), but even with a known scheduling policy, the actual allocation will depend on the availability of various resources (typically I/O) and user inputs; therefore it is never readily predictable.
Thread-safe design replaces racing situations with choreographed access to shared data. In Java, thread-safe design gets a lot of support from the underlying language (through synchronized blocks) and the standard class library (through the wait/notify mechanism in Object). The simplest way to make a thread-safe design is to use synchronized blocks so that only one client can access the server
at any one time. All other clients must wait until the server finishes with that client. Obviously, such a solution doesn't
scale well as more clients are added, since clients will have to wait a long time for their turn to access the server.
In fact, if a poorly implemented server entered an infinite loop during a call by one client, then all its other clients would wait forever. This is an example of a general problem called starvation, which has to do with situations where a client never finishes its task because one or more other clients have monopolized the resource it needs.
In general, a client shouldn't monopolize a resource that it isn't actively using. When a client has a resource but then blocks
while waiting for another resource (typically for I/O), Java will allow a second client to access the server. This situation
makes sense, and is often desirable, but must be taken into account in the choreography since the first client may not actually
have been done with its task, and the second client may want to access the same resources. The way around this is for the
server design to use wait() and notify() calls within the synchronized blocks, so that the various clients know when it's safe to proceed.
If resource A on the server is independent of resource B, it is reasonable for the server to allow one client to use A at the same time that it allows another client to use B (rather than having the second client wait for the first one to finish with A before it can use B).