Chapter 13: Concurrency Why Concurrency To Single Responsibil
This page is a generated reference surface for selective reading. It exists to keep the learner apps guide-first while still preserving source access.
Learning objectives
- Explain the main ideas and vocabulary in Concurrency Why Concurrency To Single Responsibil.
- Work through the source examples for Concurrency Why Concurrency To Single Responsibil without depending on raw chunk order.
- Use Concurrency Why Concurrency To Single Responsibil as selective reference when learner modules point back to Clean Code.
Prerequisites
- Earlier prerequisite concepts leading into Chapter 13: Concurrency Why Concurrency To Single Responsibil.
Module targets
module-03-clean-code
AI companion modes
- Explain simply
- Socratic tutor
- Quiz me
- Challenge my understanding
- Diagnose my confusion
- Generate extra practice
- Revision mode
- Connect forward / backward
Source-of-truth note
This unit is anchored to Clean Code and the source chapter "Chapter 13: Concurrency Why Concurrency To Single Responsibil". Use external resources only to clarify, extend, or modernize details without replacing the chapter's conceptual spine.
External enrichment
No chapter-specific enrichment resources are curated yet. Add them in the unit manifest when a source clearly improves learning.
Source provenance
- Primary source:
Clean Code - Source chapter 13: Chapter 13: Concurrency Why Concurrency To Single Responsibil
- Raw source file:
050-chapter-13-concurrency-why-concurrency-to-single-responsibil.md
Merged source
Chapter 13 Concurrency Why Concurrency To Single Responsibil
Chapter 13: Concurrency: Why Concurrency? to Single Responsibility Principle
Chapter 13: Concurrency
by Brett L. Schuchert
"Objects are abstractions of processing. Threads are abstractions of schedule."
-James O. Coplien1
- Private correspondence.
Writing clean concurrent programs is hard-very hard. It is much easier to write code that executes in a single thread. It is also easy to write multithreaded code that looks fine on the surface but is broken at a deeper level. Such code works fine until the system is placed under stress. In this chapter we discuss the need for concurrent programming, and the difficulties it presents. We then present several recommendations for dealing with those difficulties, and writing clean concurrent code. Finally, we conclude with issues related to testing concurrent code. Clean Concurrency is a complex topic, worthy of a book by itself. Our strategy in this book is to present an overview here and provide a more detailed tutorial in "Concurrency II" on page 317. If you are just curious about concurrency, then this chapter will suffice for you now. If you have a need to understand concurrency at a deeper level, then you should read through the tutorial as well.
Why Concurrency?
Concurrency is a decoupling strategy. It helps us decouple what gets done from when it gets done. In single-threaded applications what and when are so strongly coupled that the state of the entire application can often be determined by looking at the stack backtrace. A programmer who debugs such a system can set a breakpoint, or a sequence of breakpoints, and know the state of the system by which breakpoints are hit. Decoupling what from when can dramatically improve both the throughput and structures of an application. From a structural point of view the application looks like many little collaborating computers rather than one big main loop. This can make the system easier to understand and offers some powerful ways to separate concerns. Consider, for example, the standard "Servlet" model of Web applications. These systems run under the umbrella of a Web or EJB container that partially manages concurrency for you. The servlets are executed asynchronously whenever Web requests come in. The servlet programmer does not have to manage all the incoming requests. In principle, each servlet execution lives in its own little world and is decoupled from all the other servlet executions. Of course if it were that easy, this chapter wouldn't be necessary. In fact, the decoupling provided by Web containers is far less than perfect. Servlet programmers have to be very aware, and very careful, to make sure their concurrent programs are correct. Still, the structural benefits of the servlet model are significant. But structure is not the only motive for adopting concurrency. Some systems have response time and throughput constraints that require hand-coded concurrent solutions. For example, consider a single-threaded information aggregator that acquires information from many different Web sites and merges that information into a daily summary. Because this system is single threaded, it hits each Web site in turn, always finishing one before starting the next. The daily run needs to execute in less than 24 hours. However, as more and more Web sites are added, the time grows until it takes more than 24 hours to gather all the data. The single-thread involves a lot of waiting at Web sockets for I/O to complete. We could improve the performance by using a multithreaded algorithm that hits more than one Web site at a time. Or consider a system that handles one user at a time and requires only one second of time per user. This system is fairly responsive for a few users, but as the number of users increases, the system's response time increases. No user wants to get in line behind 150 others! We could improve the response time of this system by handling many users concurrently. Or consider a system that interprets large data sets but can only give a complete solution after processing all of them. Perhaps each data set could be processed on a different computer, so that many data sets are being processed in parallel.
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 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.
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. 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 often requires a fundamental change in design strategy.
Challenges
What makes concurrent programming so difficult? Consider the following trivial class:
public class X {
private int lastIdUsed;
public int getNextId() {
return ++lastIdUsed;
}
}
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 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.
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. A quick answer, working with just the generated byte-code, is that there are 12,870 different possible execution paths4 for those two threads executing within the getNextId method. If the type of lastIdUsed is changed from int to long, the number of possible paths increases to 2,704,156. Of course most of those paths generate valid results. The problem is that some of them don't.
Concurrency Defense Principles
What follows is a series of principles and techniques for defending your systems from the problems of concurrent code.
- Cosmic-rays, glitches, and so on. 3. See "Digging Deeper" on page 323. 4. See "Possible Paths of Execution" on page 321.
Single Responsibility Principle
The SRP5 states that a given method/class/component should have a single reason to change. Concurrency design is complex enough to be a reason to change in it's own right and therefore deserves to be separated from the rest of the code. Unfortunately, it is all too common for concurrency implementation details to be embedded directly into other production code. Here are a few things to consider:
-
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