Java concurrency in practice brian goetz download




















If the logger thread falls behind, the BlockingQueue eventually blocks the producers until the logger thread catches up. Producer-consumer logging service with no shutdown support. For a service like LogWriter to be useful in production, we need a way to terminate the logger thread so it does not prevent the JVM from shutting down 7.

Stopping a thread-based service normally. However, simply making the logger thread exit is not a very satifying shutdown mechanism. Such an abrupt shutdown discards log messages that might be waiting to be written to the log, but, more importantly, threads blocked in log because the queue is full will never become unblocked.

Cancelling a producerconsumer activity requires cancelling both the producers and the consumers. Interrupting the logger thread deals with the consumer, but because the producers in this case are not dedicated threads, cancelling them is harder.

However, this approach has race conditions that make it unreliable. The implementation of log is a check-then-act sequence: producers could observe that the service has not yet been shut down but still queue messages after the shutdown, again with the risk that the producer might get blocked in log and never become unblocked.

There are tricks that reduce the likelihood of this like having the consumer wait several seconds before declaring the queue drained , but these do not change the fundamental problem, merely the likelihood that it will cause a failure. Unreliable way to add shutdown support to the logging service.

In an abrupt shutdown, shutdownNow returns the list of tasks that had not yet started after attempting to cancel all actively executing tasks.

Adding reliable cancellation to LogWriter. Stopping a thread-based service The two different termination options offer a tradeoff between safety and responsiveness: abrupt termination is faster but riskier because tasks may be interrupted in the middle of execution, and normal termination is slower but safer because the ExecutorService does not shut down until all queued tasks are processed. Other thread-owning services should consider providing a similar choice of shutdown modes.

Simple programs can get away with starting and shutting down a global ExecutorService from main. More sophisticated programs are likely to encapsulate an ExecutorService behind a higher-level service that provides its own lifecycle methods, such as the variant of LogService in Listing 7. Encapsulating an ExecutorService extends the ownership chain from application to service to thread by adding another link; each member of the chain manages the lifecycle of the services or threads it owns.

Logging service that uses an ExecutorService. IndexingService in Listings 7. Shutdown with poison pill. Poison pills work only when the number of producers and consumers is known. The approach in IndexingService can be extended to multiple producers by having each producer place a pill on the queue and having the consumer stop only when it receives Nproducers pills.

It can be extended to multiple consumers by having each producer place Nconsumers pills on the queue, though this can get unwieldy with large numbers of producers and consumers.

Poison pills work reliably only with unbounded queues. The invokeAll and invokeAny methods can often be useful in such situations. The checkMail method in Listing 7. It creates a private executor and submits a task for each host: it then shuts down the executor and waits for termination, which occurs when all 7.

Producer thread for IndexingService. Consumer thread for IndexingService. Cancellation and Shutdown the mail-checking tasks have completed. Using a private Executor whose lifetime is bounded by a method call. This means that there is no way of knowing the state of the tasks in progress at shutdown time unless the tasks themselves perform some sort of checkpointing.

By encapsulating an ExecutorService and instrumenting execute and similarly submit , not shown to remember 4. The Runnable objects returned by shutdownNow might not be the same objects that were submitted to the ExecutorService: they might be wrapped instances of the submitted tasks.

Unfortunately, there is no shutdown option in which tasks not yet started are returned to the caller but tasks in progress are allowed to complete; such an option would eliminate this uncertain intermediate state. Stopping a thread-based service which tasks were cancelled after shutdown, TrackingExecutor can identify which tasks started but did not complete normally.

After the executor terminates, getCancelledTasks returns the list of cancelled tasks. ExecutorService that keeps track of cancelled tasks after shutdown. WebCrawler in Listing 7. The work of a web crawler is often unbounded, so if a crawler must be shut down we might want to save its state so it can be restarted later. When the crawler is shut down, both the tasks that did not start and those that were cancelled are scanned and their URLs recorded, so that page-crawling tasks for those URLs can be added to the queue when the crawler restarts.

This Chapter 7. Handling abnormal thread termination arises because the thread pool could be shut down between when the last instruction of the task executes and when the pool records the task as complete. This is not a problem if tasks are idempotent if performing them twice has the same effect as performing them once , as they typically are in a web crawler.

Otherwise, the application retrieving the cancelled tasks must be aware of this risk and be prepared to deal with false positives. Failure of a thread in a concurrent application is not always so obvious. The stack trace may be printed on the console, but no one may be watching the console. Also, when a thread fails, the application may appear to continue to work, so its failure could go unnoticed.

The leading cause of premature thread death is RuntimeException. Because these exceptions indicate a programming error or other unrecoverable problem, they are generally not caught.

Instead they propagate all the way up the stack, at which point the default behavior is to print a stack trace on the console and let the thread terminate. But losing the event dispatch thread in a GUI application would be quite noticeable—the application would stop processing events and the GUI would freeze. OutOfTime on page showed a serious consequence of thread leakage: the service represented by the Timer is permanently out of commission. Just about any code can throw a RuntimeException.

Whenever you call another method, you are taking a leap of faith that it will return normally or throw one of the checked exceptions its signature declares. The less familiar you are with the code being called, the more skeptical you should be about its behavior. Task-processing threads such as the worker threads in a thread pool or the Swing event dispatch thread spend their whole life calling unknown code through an abstraction barrier like Runnable, and these threads should be very skeptical that the code they call will be well behaved.

It would be very bad if a service like the Swing event thread failed just because some poorly written event handler threw a NullPointerException. Accordingly, these facilities should call tasks within a try-catch block that catches unchecked exceptions, or within a try-finally block to ensure that if the thread exits abnormally the framework is informed of this and can take corrective action.

This is one of the few times when you might want to consider catching RuntimeException—when you are calling unknown, untrusted code through an abstraction such as Runnable. There is some controversy over the safety of this technique; when a thread throws an unchecked Chapter 7.

Cancellation and Shutdown Listing 7. If a task throws an unchecked exception, it allows the thread to die, but not before notifying the framework that the thread has died. The framework may then replace the worker thread with a new thread, or may choose not to because the thread pool is being shut down or there are already enough worker threads to meet current demand. If you are writing a worker thread class that executes submitted tasks, or calling untrusted external code such as dynamically loaded plugins , use one of these approaches to prevent a poorly written task or plugin from taking down the thread that happens to call it.

Typical thread-pool worker thread structure. The Thread API also provides the UncaughtExceptionHandler facility, which lets you detect when a thread dies due to an uncaught exception. The two approaches are complementary: taken together, they provide defense-indepth against thread leakage.

When a thread exits due to an uncaught exception, the JVM reports this event to an application-provided UncaughtExceptionHandler see Listing 7. But the alternative—shutting down the entire application—is usually not practical. Before Java 5. In Java 5. The default handler implementation in ThreadGroup delegates to its parent thread group, and so on up the chain until one of the ThreadGroup handlers deals with the uncaught exception or it bubbles up to the toplevel thread group.

The top-level thread group handler delegates to the default system handler if one exists; the default is none and otherwise prints the stack trace to the console. UncaughtExceptionHandler interface. What the handler should do with an uncaught exception depends on your quality-of-service requirements.

The most common response is to write an error message and stack trace to the application log, as shown in Listing 7. Handlers can also take more direct action, such as trying to restart the thread, shutting down the application, paging an operator, or other corrective or diagnostic action. UncaughtExceptionHandler that logs the exception. In long-running applications, always use uncaught exception handlers for all threads that at least log the exception.

If a task submitted with submit terminates with an exception, it is rethrown by Future. Cancellation and Shutdown 7. While this is the standard and preferred way for the JVM to shut down, it can also be shut down abruptly by calling Runtime. Shutdown hooks are unstarted threads that are registered with Runtime. The JVM makes no guarantees on the order in which shutdown hooks are started. If any application threads daemon or nondaemon are still running at shutdown time, they continue to run concurrently with the shutdown process.

The JVM makes no attempt to stop or interrupt any application threads that are still running at shutdown time; they are abruptly terminated when the JVM eventually halts. Shutdown hooks should be thread-safe: they must use synchronization when accessing shared data and should be careful to avoid deadlock, just like any other concurrent code.

Further, they should not make assumptions about the state of the application such as whether other services have shut down already or all normal threads have completed or about why the JVM is shutting down, and must therefore be coded extremely defensively. Finally, they should exit as quickly as possible, since their existence delays JVM termination at a time when the user may be expecting the JVM to terminate quickly. Listing 7. To avoid this problem, shutdown hooks should not rely on services that can be shut down by the application or other shutdown hooks.

One way to accomplish this is to use a single shutdown hook for all services, rather than one for each service, and have it call a series of shutdown actions. This ensures that shutdown actions execute sequentially in a single thread, thus avoiding the possibility of race conditions or deadlock between shutdown actions. This technique can be used whether or not you use shutdown hooks; executing shutdown actions sequentially rather than concurrently eliminates many potential sources of failure.

In applications 7. Registering a shutdown hook to stop the logging service. This is what daemon threads are for.

Threads are divided into two types: normal threads and daemon threads. When the JVM starts up, all the threads it creates such as garbage collector and other housekeeping threads are daemon threads, except the main thread. When a new thread is created, it inherits the daemon status of the thread that created it, so by default any threads created by the main thread are also normal threads.

Normal threads and daemon threads differ only in what happens when they exit. When a thread exits, the JVM performs an inventory of running threads, and if the only threads that are left are daemon threads, it initiates an orderly shutdown. When the JVM halts, any remaining daemon threads are abandoned— finally blocks are not executed, stacks are not unwound—the JVM just exits. Daemon threads should be used sparingly—few processing activities can be safely abandoned at any time with no cleanup.

Daemon threads are not a good substitute for properly managing the lifecycle of services within an application. To assist in Chapter 7. Cancellation and Shutdown this, the garbage collector treats objects that have a nontrivial finalize method specially: after they are reclaimed by the collector, finalize is called so that persistent resources can be released. Summary End-of-lifecycle issues for tasks, threads, services, and applications can add complexity to their design and implementation.

Java does not provide a preemptive mechanism for cancelling activities or terminating threads. Instead, it provides a cooperative interruption mechanism that can be used to facilitate cancellation, but it is up to you to construct protocols for cancellation and use them consistently.

Chapter 7 covered some of the messy details of service lifecycle that arise from using the task execution framework in real applications. Like many attempts at decoupling complex processes, this was a bit of an overstatement. The most well behaved tasks are independent: those that do not depend on the timing, results, or side effects of other tasks. On the other hand, when you submit tasks that depend on other tasks to a thread pool, you implicitly create constraints on the execution policy that must be carefully managed to avoid liveness problems see Section 8.

Single-threaded executors make stronger promises about concurrency than do arbitrary thread pools. They guarantee that tasks are not executed concurrently, which allows you to relax the thread safety of task code.

This forms an implicit coupling between the task and the execution policy—the tasks re- Chapter 8. Applying Thread Pools quire their executor to be single-threaded. Response-time-sensitive tasks. GUI applications are sensitive to response time: users are annoyed at long delays between a button click and the corresponding visual feedback. Submitting a long-running task to a single-threaded executor, or submitting several long-running tasks to a thread pool with a small number of threads, may impair the responsiveness of the service managed by that Executor.

Tasks that use ThreadLocal. The standard Executor implementations may reap idle threads when demand is low and add new ones when demand is high, and also replace a worker thread with a fresh one if an unchecked exception is thrown from a task.

ThreadLocal makes sense to use in pool threads only if the thread-local value has a lifetime that is bounded by that of a task; ThreadLocal should not be used in pool threads to communicate values between tasks.

Thread pools work best when tasks are homogeneous and independent. Document these requirements so that future maintainers do not undermine safety or liveness by substituting an incompatible execution policy. In a single-threaded executor, a task that submits another task to the same executor and waits for its result will always deadlock.

The requirement is not quite this strong; it would be enough to ensure only that tasks not execute concurrently and provide enough synchronization so that the memory effects of one task are guaranteed to be visible to the next task—which is precisely the guarantee offered by newSingleThreadExecutor. Implicit couplings waiting for the result of the second task. The same thing can happen in larger thread pools if all threads are executing tasks that are blocked waiting for other tasks still on the work queue.

This is called thread starvation deadlock, and can occur whenever a pool task initiates an unbounded blocking wait for some resource or condition that can succeed only through the action of another pool task, such as waiting for the return value or side effect of another task, unless you can guarantee that the pool is large enough.

ThreadDeadlock in Listing 8. With a single-threaded executor, ThreadDeadlock will always deadlock. Similarly, tasks coordinating amongst themselves with a barrier could also cause thread starvation deadlock if the pool is not big enough. In addition to any explicit bounds on the size of a thread pool, there may also be implicit limits because of constraints on other resources.

If your application uses a JDBC connection pool with ten connections and each task needs a database connection, it is as if your thread pool only has ten threads because tasks in excess of ten will block waiting for a connection. Task that deadlocks in a single-threaded Executor. Chapter 8. Applying Thread Pools 8. A thread pool can become clogged with long-running tasks, increasing the service time even for short tasks.

If the pool size is too small relative to the expected steady-state number of longrunning tasks, eventually all the pool threads will be running long-running tasks and responsiveness will suffer. One technique that can mitigate the ill effects of long-running tasks is for tasks to use timed resource waits instead of unbounded waits. Most blocking methods in the plaform libraries come in both untimed and timed versions, such as Thread. If the wait times out, you can mark the task as failed and abort it or requeue it for execution later.

This guarantees that each task eventually makes progress towards either successful or failed completion, freeing up threads for tasks that might complete more quickly. If a thread pool is frequently full of blocked tasks, this may also be a sign that the pool is too small. If a thread pool is too big, then threads compete for scarce CPU and memory resources, resulting in higher memory usage and possible resource exhaustion.

If it is too small, throughput suffers as processors go unused despite available work. To size a thread pool properly, you need to understand your computing environment, your resource budget, and the nature of your tasks. How many processors does the deployment system have? How much memory? Do they require a scarce resource, such as a JDBC connection? If you have different categories of tasks with very different behaviors, consider using multiple thread pools so each can be tuned according to its workload.

Alternatively, the size of the thread pool can be tuned 8. Calculating pool size constraints for these types of resources is easier: just add up how much of that resource each task requires and divide that into the total quantity available.

The result will be an upper bound on the pool size. When tasks require a pooled resource such as database connections, thread pool size and resource pool size affect each other. If each task requires a connection, the effective size of the thread pool is limited by the connection pool size. Similarly, when the only consumers of connections are pool tasks, the effective size of the connection pool is limited by the thread pool size. ThreadPoolExecutor has several constructors, the most general of which is shown in Listing 8.

The core size is the target size; the implementation attempts to maintain the pool at this size even when there are no tasks to execute,2 and will 2. When a ThreadPoolExecutor is initially created, the core threads are not started immediately but instead as tasks are submitted, unless you call prestartAllCoreThreads. General constructor for ThreadPoolExecutor. A thread that has been idle for longer than the keep-alive time becomes a candidate for reaping and can be terminated if the current pool size exceeds the core size.

By tuning the core pool size and keep-alive times, you can encourage the pool to reclaim resources used by otherwise idle threads, making them available for more useful work. Like everything else, this is a tradeoff: reaping idle threads incurs additional latency due to thread creation if threads must later be created when demand increases. Other combinations are possible using the explicit ThreadPoolExecutor constructor. We saw in Section 6. However, this is only a partial solution; it is still possible for the application to run out of resources under heavy load, just harder.

If the arrival rate for new requests exceeds the rate at which they can be 3. If the pool is already at the core size, ThreadPoolExecutor creates a new thread only if the work queue is full.

In Java 6, allowCoreThreadTimeOut allows you to request that all pool threads be able to time out; enable this feature with a core size of zero if you want a bounded thread pool with a bounded work queue but still have all the threads torn down when there is no work to do. With a thread pool, they wait in a queue of Runnables managed by the Executor instead of queueing up as threads contending for the CPU. Representing a waiting task with a Runnable and a list node is certainly a lot cheaper than with a thread, but the risk of resource exhaustion still remains if clients can throw requests at the server faster than it can handle them.

Requests often arrive in bursts even when the average request rate is fairly stable. Queues can help smooth out transient bursts of tasks, but if tasks continue to arrive too quickly you will eventually have to throttle the arrival rate to avoid running out of memory.

ThreadPoolExecutor allows you to supply a BlockingQueue to hold tasks awaiting execution. There are three basic approaches to task queueing: unbounded queue, bounded queue, and synchronous handoff. Tasks will queue up if all worker threads are busy, but the queue could grow without bound if the tasks keep arriving faster than they can be executed.

Bounded queues help prevent resource exhaustion but introduce the question of what to do with new tasks when the queue is full. There are a number of possible saturation policies for addressing this problem; see Section 8. With a bounded work queue, the queue size and pool size must be tuned together. A large queue coupled with a small pool can help reduce memory usage, CPU usage, and context switching, at the cost of potentially constraining throughput.

For very large or unbounded pools, you can also bypass queueing entirely and instead hand off tasks directly from producers to worker threads using a SynchronousQueue. A SynchronousQueue is not really a queue at all, but a mechanism for managing handoffs between threads.

In order to put an element on a SynchronousQueue, another thread must already be waiting to accept the handoff.

If no thread is waiting but the current pool size is less than the maximum, ThreadPoolExecutor creates a new thread; otherwise the task is rejected according to the saturation policy. SynchronousQueue is a practical choice only if the pool is unbounded or if rejecting excess tasks is acceptable.

For more control over task execution order, you can use a PriorityBlockingQueue, which 4. Applying Thread Pools orders tasks according to priority. Bounding either the thread pool or the work queue is suitable only when tasks are independent. The saturation policy is also used when a task is submitted to an Executor that has been shut down. The discard policy silently discards the newly submitted task if it cannot be queued for execution; the discard-oldest policy discards the task that would otherwise be executed next and tries to resubmit the new task.

If the work queue is a priority queue, this discards the highest-priority element, so the combination of a discard-oldest saturation policy and a priority queue is not a good one. It executes the newly submitted task not in a pool thread, but in the thread that calls execute. Since 5. SynchronousQueue was replaced in Java 6 with a new nonblocking algorithm that improved throughput in Executor benchmarks by a factor of three over the Java 5. The main thread would also not be calling accept during this time, so incoming requests will queue up in the TCP layer instead of in the application.

If the overload persisted, eventually the TCP layer would decide it has queued enough connection requests and begin discarding connection requests as well. As the server becomes overloaded, the overload is gradually pushed outward— from the pool threads to the work queue to the application to the TCP layer, and eventually to the client—enabling more graceful degradation under load.

Choosing a saturation policy or making other changes to the execution policy can be done when the Executor is created. Listing 8. CallerRunsPolicy ; Listing 8.

However, the same effect can be accomplished by using a Semaphore to bound the task injection rate, as shown in BoundedExecutor in Listing 8. ThreadFactory has a single method, newThread, that is called whenever a thread pool needs to create a new thread. There are a number of reasons to use a custom thread factory.

You might want to specify an UncaughtExceptionHandler for pool threads, or instantiate an instance of a custom Thread class, such as one that performs debug logging. You might want to modify the priority generally not a very good idea; see Section Or maybe you just want to give pool threads more meaningful names to simplify interpreting thread dumps and error logs. Using a Semaphore to throttle task submission.

ThreadFactory interface. MyAppThread can also be used elsewhere in the application so that all threads can take advantage of its debugging features. Custom thread factory.

The interesting customization takes place in MyAppThread, shown in Listing 8. If your application takes advantage of security policies to grant permissions to particular codebases, you may want to use the privilegedThreadFactory factory method in Executors to construct your thread factory. It creates pool threads that have the same permissions, AccessControlContext, and contextClassLoader as the thread creating the privilegedThreadFactory.

Otherwise, threads created by the thread pool inherit permissions from whatever client happens to be calling execute or submit at the time a new thread is needed, which could cause confusing security-related exceptions. If the Executor is created through one of the factory methods in Executors except newSingleThreadExecutor , you can cast the result to ThreadPoolExecutor to access the setters as in Listing 8. While Chapter 8. Custom thread base class. Modifying an Executor created with the standard factories.

If some misguided code were to increase the pool size on a single-threaded executor, it would undermine the intended execution semantics. The beforeExecute and afterExecute hooks are called in the thread that executes the task, and can be used for adding logging, timing, monitoring, or statistics gathering. The afterExecute hook is called whether the task completes by returning normally from run or by throwing an Exception.

If the task completes with an Error , afterExecute is not called. If beforeExecute throws a RuntimeException, the task is not executed and afterExecute is not called. Because execution hooks are called in the thread that executes the task, a value placed in a ThreadLocal by beforeExecute can be retrieved by afterExecute. TimingThreadPool uses a pair of AtomicLongs to keep track of the total number of tasks processed and the total processing time, and uses the terminated hook to print a log message showing the average task time.

Thread pool extended with logging and timing. Parallelizing recursive algorithms 8. Transforming sequential execution into parallel execution. A call to processInParallel returns more quickly than a call to processSequentially because it returns as soon as all the tasks are queued to the Executor, rather than waiting for them all to complete.

If you want to submit a set of tasks and wait for them all to complete, you can use ExecutorService. Loop parallelization can also be applied to some recursive designs; there are often sequential loops within the recursive algorithm that can be parallelized in the same manner as Listing 8. The easier case is when each iteration does not require the results of the recursive iterations it invokes.

For example, sequentialRecursive in Listing 8. Applying Thread Pools calculation on each node and placing the result in a collection. Transforming sequential tail-recursion into parallelized recursion. When parallelRecursive returns, each node in the tree has been visited the traversal is still sequential: only the calls to compute are executed in parallel and the computation for each node has been queued to the Executor.

Waiting for results to be calculated in parallel. The rule set has two parts: computing the list of legal moves from a given position and computing the result of applying a move to a position. Puzzle in Listing 8. From this interface, we can write a simple sequential solver that searches the puzzle space until a solution is found or the puzzle space is exhausted.

Node in Listing 8. Following the links back from a Node lets us reconstruct the sequence of moves that led to the current position. SequentialPuzzleSolver in Listing 8. Rewriting the solver to exploit concurrency would allow us to compute next moves and evaluate the goal condition in parallel, since the process of evaluating one move is mostly independent of evaluating other moves.

ConcurrentPuzzleSolver in Listing 8. Most of the work is done in run: evaluating the set of possible next positions, pruning positions already searched, evaluating whether success has yet been achieved by this task or by some other task , and submitting unsearched positions to an Executor. This provides thread safety and avoids the race condition inherent in conditionally updating a shared collection by using putIfAbsent to atomically 7.

Link node for the puzzle solver framework. ConcurrentPuzzleSolver uses the internal work queue of the thread pool instead of the call stack to hold the state of the search. The concurrent approach also trades one form of limitation for another that might be more suitable to the problem domain. These requirements describe a sort of latch see Section 5. We could easily build a blocking resultbearing latch using the techniques in Chapter 14, but it is often easier and less error-prone to use existing library classes rather than low-level language mechanisms.

ValueLatch in Listing 8. The main thread needs to wait until a solution is found; getValue in ValueLatch blocks until some thread has set the value.

To avoid having to deal with RejectedExecu- 8. Sequential puzzle solver. Concurrent version of puzzle solver. Result-bearing latch used by ConcurrentPuzzleSolver. ConcurrentPuzzleSolver does not deal well with the case where there is no solution: if all possible moves and positions have been evaluated and no solution has been found, solve waits forever in the call to getSolution.

One possible solution is to keep a count of active solver tasks and set the solution to null when the count drops to zero, as in Listing 8. Finding the solution may also take longer than we are willing to wait; there are several additional termination conditions we could impose on the solver.

One is a time limit; this is easily done by implementing a timed getValue in ValueLatch which would use the timed version of await , and shutting down the Executor and declaring failure if getValue times out.

Or we can provide a cancellation mechanism and let the client make its own Chapter 8. Solver that recognizes when no solution exists. It offers a number of tuning options, such as policies for creating and tearing down threads, handling queued tasks, and what to do with excess tasks, and provides several hooks for extending its behavior.

To maintain safety, certain tasks must run in the Swing event thread. But you cannot execute longrunning tasks in the event thread, lest the UI become unresponsive.

If you are not planning to write a totally single-threaded program, there will be activities that run partially in an application thread and partially in the event thread.

Like many other threading bugs, getting this division wrong may not necessarily make your program crash immediately; instead, it could behave oddly under hard-to-identify conditions.

Even though the GUI frameworks themselves are single-threaded subsystems, your application may not be, and you still need to consider threading issues carefully when writing GUI code. AWT originally tried to support a greater degree of multithreaded access, and the decision to make Swing single-threaded was based largely on experience with AWT.

GUI Applications Multithreaded GUI frameworks tend to be particularly susceptible to deadlock, partially because of the unfortunate interaction between input event processing and any sensible object-oriented modeling of GUI components.

Combining this tendency for activities to access the same GUI objects in the opposite order with the requirement of making each object thread-safe yields a recipe for inconsistent lock ordering, which leads to deadlock see Chapter And this is exactly what nearly every GUI toolkit development effort rediscovered through experience. Another factor leading to deadlock in multithreaded GUI frameworks is the prevalence of the model-view-control MVC pattern.

But the controller can also call into the view, which may in turn call back into the model to query the model state. The result is again inconsistent lock ordering, with the attendant risk of deadlock. I believe you can program successfully with multithreaded GUI toolkits if the toolkit is very carefully designed; if the toolkit exposes its locking methodology in gory detail; if you are very smart, very careful, and have a global understanding of the whole structure of the toolkit.

If you get one of these things slightly wrong, things will mostly work, but you will get occasional hangs due to deadlocks or glitches due to races.

This multithreaded approach works best for people who have been intimately involved in the design of the toolkit. So the authors get very disgruntled and frustrated and use bad words on the poor innocent toolkit. Why are GUIs single-threaded? Events are a kind of task; the event handling machinery provided by AWT and Swing is structurally similar to an Executor. If those other tasks are responsible for responding to user input or providing visual feedback, the application will appear to have frozen.

Therefore, tasks that execute in the event thread must return control to the event thread quickly. To update a progress indicator while a long-running task executes or provide visual feedback when it completes, you again need to execute code in the event thread. This can get complicated quickly. The upside is that tasks that run in the event thread need not worry about synchronization when accessing presentation objects; the downside is that you cannot access presentation objects from outside the event thread at all.

As with all rules, there are a few exceptions. The invokeLater and invokeAndWait methods function a lot like an Executor. In fact, it is trivial to implement the threading-related methods from SwingUtilities using a single-threaded Executor, as shown in Listing 9. This is not how SwingUtilities is actually implemented, as Swing predates the Executor framework, but is probably how it would be if Swing were being implemented today. The Swing event thread can be thought of as a single-threaded Executor that processes tasks from the event queue.

As with thread pools, sometimes the worker thread dies and is replaced by a new one, but this should be transparent to tasks. Sequential, single-threaded execution is a sensible execution policy when tasks are short-lived, scheduling predictability is not important, or it is imperative that tasks not execute concurrently.

GuiExecutor in Listing 9. Before CPiJ was published, a lot of good programmers were writing some really bad concurrency code. CPiJ taught us how to remove all of the complicated concurrency code in our programs and put them into reusable components.

However some of those constructs come with a learning curve. The purpose of Java Concurrent Animated is to provide a series of animations that visualize the functionality of the components in the java. Each animation features buttons that correspond to the method calls in that component. Each button click shows how the threads interact in real time.

If you're looking for an advanced title on concurrency programming in Java, you won't go wrong with this one. More info at Amazon. Category : Advanced Java Review by : Ulf Dittmer Rating : 8 horseshoes Java has had multi-threading capabilities from the beginning, but with the arrival of multi-core and multi-processor CPUs on desktops everywhere, and the broad range of new concurrency features in Java 5, there are no excuses any more not to take advantage of multithreading.

Getting it right can be tricky, though, and that's where this book comes in. It explains not just the features of the Java virtual machine and the class libraries that help implement concurrent applications, but also serves as an introduction to the problems arising in multi-threaded code in general.

That can range from the small -how to share a class variable between threads - to the large - how to structure applications to take advantage of concurrency. More specialized chapters deal with threading in GUIs important for Swing developers , how to maximize performance while retaining thread safety, and how to test concurrent code. All concepts are explained with plenty of code examples that show what is and what isn't thread-safe. If several ways to solve a concurrency problem exist, their functional and performance differences are investigated, and -where possible- quantified.

Engineering consists of tradeoffs everywhere, and this book makes clear how those between functionality, performance and thread safety can usefully be made. Everyone not having had the benefit of CS "Issues in Concurrency" will get a lot out of this book. And those who did will learn how to properly implement concurrent applications in Java. So many examples! I love books that really SHOW me what's going on.

When I first got this book, I said, "wow, kindof a smallish book for a big deal subject



0コメント

  • 1000 / 1000