←Older revision |
Revision as of 19:32, 11 January 2015 |
Line 30: |
Line 30: |
| | | |
| Should we think twice before claiming something is [[impossible]]? Yeah, sometimes it is hard to explain why something is [[impossible]] and there is always a lot of dummies to offer half-baken solutions. But, explaining [[impossible|impossibility]] is necessary from time to time! Btw. I still have one more topic about [[impossible|imposibility]] to cover - [[TBD|to be continued]]... | | Should we think twice before claiming something is [[impossible]]? Yeah, sometimes it is hard to explain why something is [[impossible]] and there is always a lot of dummies to offer half-baken solutions. But, explaining [[impossible|impossibility]] is necessary from time to time! Btw. I still have one more topic about [[impossible|imposibility]] to cover - [[TBD|to be continued]]... |
| + | |
| + | == Careful modular design is key == |
| + | |
| + | Contributed by Jeffrey D. Smith, author of the JavaMutex project on SourceForge. |
| + | |
| + | Avoiding [[Deadlock]] in a modularized system requires removing the tightly coupled connections between modules, and replacing with asynchronous communications for requesting services and receiving responses. Each module is a conceptual server thread, awaiting on requests from any number of other asynchronous threads, as well awaiting on responses to its own requests for services from other servers. Deadlock is most commonly caused by holding read-write (exclusive) locks while calling foreign code and that code acquires locks and then calls (directly or indirectly) the originating module. Thus, a circular reference is created and causes a deadly embrace. A partial solution is to rely on read-only (shared) locks as much as possible, but that cannot prevent an arbitrary thread from "hanging" (awaiting indefinitely) on the read-write lock while other threads are concurrently holding the read-only lock. Whenever a thread must await on a resource, only the mutex for that resource is atomically released. Any other held mutex resources remain held while awaiting, thus leading to potential deadlock. |
| + | |
| + | The solution requires a comprehensive design to eliminate holding locks while the thread is accessing other modules. |
| + | |
| + | A potential solution is to restrict direct access to read-only data for foreign modules. When foreign data must be accessed and updated by asynchronous threads, then the processing is isolated to the owning thread (server). Thus, the various module servers encapsulate and isolate the updating of information and won't need to hold any locks for the update. The "client" threads send asynchronous requests to the owning server and await for a reply. Each server simply awaits for service requests, processes each request by computing a result or by sending one or more service requests to the other servers and combines the responses. The combined results are then returned to the original requestor. |
| + | |
| + | The server can delegate a potentially time consuming request to a private worker thread. The server maintains its responsiveness to other service requests by offloading "big" requests to its private pool of worker threads, which operate as a "mini-servers" for the parent server. |
| + | |
| + | The main Java issue faced by modularized systems is that the Java synchronization mechanism supports any number of threads awaiting on a single resource for exclusive control. By encapsulating the Java synchronization mechanism, the inverse design is abstracted: A single thread awaiting on multiple events (logical resources). The key is provided by the JavaMutex EventTokenSet class that is designed for one thread to await on multiple events across multiple threads. Thus, a server can await for multiple events occurring in multiple other threads. |
| + | |
| + | A critical design point is to avoid replacing potential deadlocks with potential infinite cycles of service requests. Server Alpha requests data from Server Beta. Server Beta requests data from Server Alpha, which triggers yet another request to Server Beta and so on. The deadlock is replaced by OutOfMemoryError as event tokens are created for each request. Thus, each service module must be designed to demonstrate a terminus for every possible service request. |
| + | |
| + | The JavaMutex project is a library of 100% pure Java interfaces and classes that provide concurrency tools for locks, intersects, latches, executor services, etc. There is a sample test case TestMultiServer.java that demonstrates a simple modularized system of two servers and a client, each running asynchronously as its own thread. The client issues multiple requests to the servers, and the servers issue further requests amongst themselves. Responses are accumulated asynchronously and replied to the client. All of the processing is free of deadlocks and completely asynchronous. The asynchronous nature of clients and servers is the basic design principle. The design is scalable and only limited by the number of distinct threads (and memory) available in the JVM. |
| + | |
| + | Trying to retrofit a mature, yet deadlock-prone modularized system is very difficult. The correct initial design is crucial to building a sophisticated modularized system with asynchronous server modules that is free of deadlocks. |