mirror of
https://github.com/glen9527/Clean-Code-zh.git
synced 2025-12-17 18:54:23 +08:00
md 引入图片资源
This commit is contained in:
107
docs/ch13.md
107
docs/ch13.md
@@ -1,7 +1,7 @@
|
||||
# 第 13 章 Concurrency
|
||||
by Brett L. Schuchert
|
||||
|
||||
Image
|
||||

|
||||
|
||||
“Objects are abstractions of processing. Threads are abstractions of schedule.”
|
||||
|
||||
@@ -33,26 +33,24 @@ Or consider a system that interprets large data sets but can only give a complet
|
||||
Myths and Misconceptions
|
||||
And so there are compelling reasons to adopt concurrency. However, as we said before, concurrency is hard. If you aren’t very careful, you can create some very nasty situations. Consider these common myths and misconceptions:
|
||||
|
||||
• Concurrency always improves performance.
|
||||
- Concurrency always improves performance.
|
||||
Concurrency can sometimes improve performance, but only when there is a lot of wait time that can be shared between multiple threads or multiple processors. Neither situation is trivial.
|
||||
|
||||
• Design does not change when writing concurrent programs.
|
||||
- Design does not change when writing concurrent programs.
|
||||
In fact, the design of a concurrent algorithm can be remarkably different from the design of a single-threaded system. The decoupling of what from when usually has a huge effect on the structure of the system.
|
||||
|
||||
• Understanding concurrency issues is not important when working with a container such as a Web or EJB container.
|
||||
- Understanding concurrency issues is not important when working with a container such as a Web or EJB container.
|
||||
In fact, you’d better know just what your container is doing and how to guard against the issues of concurrent update and deadlock described later in this chapter.
|
||||
|
||||
Here are a few more balanced sound bites regarding writing concurrent software:
|
||||
|
||||
• Concurrency incurs some overhead, both in performance as well as writing additional code.
|
||||
|
||||
• Correct concurrency is complex, even for simple problems.
|
||||
|
||||
• Concurrency bugs aren’t usually repeatable, so they are often ignored as one-offs2 instead of the true defects they are.
|
||||
- Concurrency incurs some overhead, both in performance as well as writing additional code.
|
||||
- Correct concurrency is complex, even for simple problems.
|
||||
- Concurrency bugs aren’t usually repeatable, so they are often ignored as one-offs2 instead of the true defects they are.
|
||||
|
||||
2. Cosmic-rays, glitches, and so on.
|
||||
|
||||
• Concurrency often requires a fundamental change in design strategy.
|
||||
- Concurrency often requires a fundamental change in design strategy.
|
||||
|
||||
CHALLENGES
|
||||
What makes concurrent programming so difficult? Consider the following trivial class:
|
||||
@@ -67,11 +65,11 @@ What makes concurrent programming so difficult? Consider the following trivial c
|
||||
```
|
||||
Let’s say we create an instance of X, set the lastIdUsed field to 42, and then share the instance between two threads. Now suppose that both of those threads call the method getNextId(); there are three possible outcomes:
|
||||
|
||||
• Thread one gets the value 43, thread two gets the value 44, lastIdUsed is 44.
|
||||
- Thread one gets the value 43, thread two gets the value 44, lastIdUsed is 44.
|
||||
|
||||
• Thread one gets the value 44, thread two gets the value 43, lastIdUsed is 44.
|
||||
- Thread one gets the value 44, thread two gets the value 43, lastIdUsed is 44.
|
||||
|
||||
• Thread one gets the value 43, thread two gets the value 43, lastIdUsed is 43.
|
||||
- Thread one gets the value 43, thread two gets the value 43, lastIdUsed is 43.
|
||||
|
||||
The surprising third result3 occurs when the two threads step on each other. This happens because there are many possible paths that the two threads can take through that one line of Java code, and some of those paths generate incorrect results. How many different paths are there? To really answer that question, we need to understand what the Just-In-Time Compiler does with the generated byte-code, and understand what the Java memory model considers to be atomic.
|
||||
|
||||
@@ -89,11 +87,9 @@ The SRP5 states that a given method/class/component should have a single reason
|
||||
|
||||
5. [PPP]
|
||||
|
||||
• Concurrency-related code has its own life cycle of development, change, and tuning.
|
||||
|
||||
• Concurrency-related code has its own challenges, which are different from and often more difficult than nonconcurrency-related code.
|
||||
|
||||
• The number of ways in which miswritten concurrency-based code can fail makes it challenging enough without the added burden of surrounding application code.
|
||||
- Concurrency-related code has its own life cycle of development, change, and tuning.
|
||||
- Concurrency-related code has its own challenges, which are different from and often more difficult than nonconcurrency-related code.
|
||||
- The number of ways in which miswritten concurrency-based code can fail makes it challenging enough without the added burden of surrounding application code.
|
||||
|
||||
Recommendation: Keep your concurrency-related code separate from other code.6
|
||||
|
||||
@@ -102,13 +98,12 @@ Recommendation: Keep your concurrency-related code separate from other code.6
|
||||
Corollary: Limit the Scope of Data
|
||||
As we saw, two threads modifying the same field of a shared object can interfere with each other, causing unexpected behavior. One solution is to use the synchronized keyword to protect a critical section in the code that uses the shared object. It is important to restrict the number of such critical sections. The more places shared data can get updated, the more likely:
|
||||
|
||||
• You will forget to protect one or more of those places—effectively breaking all code that modifies that shared data.
|
||||
|
||||
• There will be duplication of effort required to make sure everything is effectively guarded (violation of DRY7).
|
||||
- You will forget to protect one or more of those places—effectively breaking all code that modifies that shared data.
|
||||
- There will be duplication of effort required to make sure everything is effectively guarded (violation of DRY7).
|
||||
|
||||
7. [PRAG].
|
||||
|
||||
• It will be difficult to determine the source of failures, which are already hard enough to find.
|
||||
- It will be difficult to determine the source of failures, which are already hard enough to find.
|
||||
|
||||
Recommendation: Take data encapsulation to heart; severely limit the access of any data that may be shared.
|
||||
|
||||
@@ -127,13 +122,10 @@ Recommendation: Attempt to partition data into independent subsets than can be o
|
||||
KNOW YOUR LIBRARY
|
||||
Java 5 offers many improvements for concurrent development over previous versions. There are several things to consider when writing threaded code in Java 5:
|
||||
|
||||
• Use the provided thread-safe collections.
|
||||
|
||||
• Use the executor framework for executing unrelated tasks.
|
||||
|
||||
• Use nonblocking solutions when possible.
|
||||
|
||||
• Several library classes are not thread safe.
|
||||
- Use the provided thread-safe collections.
|
||||
- Use the executor framework for executing unrelated tasks.
|
||||
- Use nonblocking solutions when possible.
|
||||
- Several library classes are not thread safe.
|
||||
|
||||
Thread-Safe Collections
|
||||
When Java was young, Doug Lea wrote the seminal book8 Concurrent Programming in Java. Along with the book he developed several thread-safe collections, which later became part of the JDK in the java.util.concurrent package. The collections in that package are safe for multithreaded situations and they perform well. In fact, the ConcurrentHashMap implementation performs better than HashMap in nearly all situations. It also allows for simultaneous concurrent reads and writes, and it has methods supporting common composite operations that are otherwise not thread safe. If Java 5 is the deployment environment, start with ConcurrentHashMap.
|
||||
@@ -142,14 +134,14 @@ When Java was young, Doug Lea wrote the seminal book8 Concurrent Programming in
|
||||
|
||||
There are several other kinds of classes added to support advanced concurrency design. Here are a few examples:
|
||||
|
||||
image
|
||||

|
||||
|
||||
Recommendation: Review the classes available to you. In the case of Java, become familiar with java.util.concurrent, java.util.concurrent.atomic, java.util.concurrent.locks.
|
||||
|
||||
KNOW YOUR EXECUTION MODELS
|
||||
There are several different ways to partition behavior in a concurrent application. To discuss them we need to understand some basic definitions.
|
||||
|
||||
image
|
||||

|
||||
|
||||
Given these definitions, we can now discuss the various execution models used in concurrent programming.
|
||||
|
||||
@@ -185,11 +177,11 @@ Recommendation: Avoid using more than one method on a shared object.
|
||||
|
||||
There will be times when you must use more than one method on a shared object. When this is the case, there are three ways to make the code correct:
|
||||
|
||||
• Client-Based Locking—Have the client lock the server before calling the first method and make sure the lock’s extent includes code calling the last method.
|
||||
- Client-Based Locking—Have the client lock the server before calling the first method and make sure the lock’s extent includes code calling the last method.
|
||||
|
||||
• Server-Based Locking—Within the server create a method that locks the server, calls all the methods, and then unlocks. Have the client call the new method.
|
||||
- Server-Based Locking—Within the server create a method that locks the server, calls all the methods, and then unlocks. Have the client call the new method.
|
||||
|
||||
• Adapted Server—create an intermediary that performs the locking. This is an example of server-based locking, where the original server cannot be changed.
|
||||
- Adapted Server—create an intermediary that performs the locking. This is an example of server-based locking, where the original server cannot be changed.
|
||||
|
||||
KEEP SYNCHRONIZED SECTIONS SMALL
|
||||
The synchronized keyword introduces a lock. All sections of code guarded by the same lock are guaranteed to have only one thread executing through them at any given time. Locks are expensive because they create delays and add overhead. So we don’t want to litter our code with synchronized statements. On the other hand, critical sections13 must be guarded. So we want to design our code with as few critical sections as possible.
|
||||
@@ -224,19 +216,13 @@ Recommendation: Write tests that have the potential to expose problems and then
|
||||
|
||||
That is a whole lot to take into consideration. Here are a few more fine-grained recommendations:
|
||||
|
||||
• Treat spurious failures as candidate threading issues.
|
||||
|
||||
• Get your nonthreaded code working first.
|
||||
|
||||
• Make your threaded code pluggable.
|
||||
|
||||
• Make your threaded code tunable.
|
||||
|
||||
• Run with more threads than processors.
|
||||
|
||||
• Run on different platforms.
|
||||
|
||||
• Instrument your code to try and force failures.
|
||||
- Treat spurious failures as candidate threading issues.
|
||||
- Get your nonthreaded code working first.
|
||||
- Make your threaded code pluggable.
|
||||
- Make your threaded code tunable.
|
||||
- Run with more threads than processors.
|
||||
- Run on different platforms.
|
||||
- Instrument your code to try and force failures.
|
||||
|
||||
Treat Spurious Failures as Candidate Threading Issues
|
||||
Threaded code causes things to fail that “simply cannot fail.” Most developers do not have an intuitive feel for how threading interacts with other code (authors included). Bugs in threaded code might exhibit their symptoms once in a thousand, or a million, executions. Attempts to repeat the systems can be frustratingly. This often leads developers to write off the failure as a cosmic ray, a hardware glitch, or some other kind of “one-off.” It is best to assume that one-offs do not exist. The longer these “one-offs” are ignored, the more code is built on top of a potentially faulty approach.
|
||||
@@ -251,13 +237,10 @@ Recommendation: Do not try to chase down nonthreading bugs and threading bugs at
|
||||
Make Your Threaded Code Pluggable
|
||||
Write the concurrency-supporting code such that it can be run in several configurations:
|
||||
|
||||
• One thread, several threads, varied as it executes
|
||||
|
||||
• Threaded code interacts with something that can be both real or a test double.
|
||||
|
||||
• Execute with test doubles that run quickly, slowly, variable.
|
||||
|
||||
• Configure tests so they can run for a number of iterations.
|
||||
- One thread, several threads, varied as it executes
|
||||
- Threaded code interacts with something that can be both real or a test double.
|
||||
- Execute with test doubles that run quickly, slowly, variable.
|
||||
- Configure tests so they can run for a number of iterations.
|
||||
|
||||
Recommendation: Make your thread-based code especially pluggable so that you can run it in various configurations.
|
||||
|
||||
@@ -287,9 +270,8 @@ Each of these methods can affect the order of execution, thereby increasing the
|
||||
|
||||
There are two options for code instrumentation:
|
||||
|
||||
• Hand-coded
|
||||
|
||||
• Automated
|
||||
- Hand-coded
|
||||
- Automated
|
||||
|
||||
Hand-Coded
|
||||
You can insert calls to wait(), sleep(), yield(), and priority() in your code by hand. It might be just the thing to do when you’re testing a particularly thorny piece of code.
|
||||
@@ -312,13 +294,10 @@ The inserted call to yield() will change the execution pathways taken by the cod
|
||||
|
||||
There are many problems with this approach:
|
||||
|
||||
• You have to manually find appropriate places to do this.
|
||||
|
||||
• How do you know where to put the call and what kind of call to use?
|
||||
|
||||
• Leaving such code in a production environment unnecessarily slows the code down.
|
||||
|
||||
• It’s a shotgun approach. You may or may not find flaws. Indeed, the odds aren’t with you.
|
||||
- You have to manually find appropriate places to do this.
|
||||
- How do you know where to put the call and what kind of call to use?
|
||||
- Leaving such code in a production environment unnecessarily slows the code down.
|
||||
- It’s a shotgun approach. You may or may not find flaws. Indeed, the odds aren’t with you.
|
||||
|
||||
What we need is a way to do this during testing but not in production. We also need to easily mix up configurations between different runs, which results in increased chances of finding errors in the aggregate.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user