Writing the right program is difficult, and writing the right concurrency program is harder. There are more error-prone areas in concurrent programs than serial programs. So why write concurrent programs? Threading is an integral and important feature of the Java language, which makes complex asynchronous code simpler and greatly simplifies the development of complex systems. In addition, the simplest way to get the most out of the powerful computing power of multiprocessor systems is to use threads. As the number of processors grows, how to use concurrency efficiently is becoming increasingly important.
1.1 History of concurrency
The presence of the operating system makes it possible for the computer to run multiple programs at a time, and different programs run in separate processes: the operating system allocates various resources for each independently executed process, including memory, file handles, and security certificates. If needed, data can be exchanged between different processes through coarse-grained communication mechanisms, including sockets, signal handlers, shared memory, semaphores, and files.
The advantages of the serial programming model lie in its intuition and simplicity, as it mimics the way humans work: One thing at a time, and then another when done.
Threads allow multiple programmatic flows to exist concurrently in the same process. Threads share process-wide resources such as memory handles and file handles, but each thread has its own program counters, stacks, and local variables. Threads also provide an intuitive decomposition pattern to take advantage of hardware parallelism in multiprocessor systems, while multiple threads in the same program can be scheduled to run concurrently on multiple CPUs.
Threads are also known as lightweight processes. In most modern operating systems, the thread is the basic dispatch unit, not the process. Without a clear synergy mechanism, threads are executed independently of each other. Because all threads in the same process share the memory address space of the process, these threads can access the same variables and allocate objects on the same heap, which requires a more granular data-sharing mechanism than sharing data between processes. If there is no explicit synchronization mechanism to coordinate access to the shared data, then when one thread is using a variable, another thread may access the variable at the same time, which will result in unpredictable results.
1.2 Advantages of threading
If used properly, threads can effectively reduce the cost of developing and maintaining programs, while improving the performance of complex applications. Threads can convert most of the asynchronous workflow into a serial workflow, thus better simulating how humans work and interact. In addition, threads can reduce the complexity of code, making code easier to write, read, and maintain.
1.2.1 The power of multi-processor
Because the basic dispatch unit is a thread, if there is only one thread in the program, it can run at most one processor at a time. On a dual-processor system, a single-threaded program can use only half of the CPU resources, while on a system with 100 processors, 99% of the resources will not be available. On the other hand, multithreaded programs can execute on multiple processors at the same time. If designed correctly, multithreaded programs can increase the throughput of the system by increasing the utilization of processor resources.
The use of multiple threads also helps to achieve higher throughput rates on a single-processor system. If the program is single-threaded, the processor will be idle when the program waits for a synchronous I/O operation to complete. In multithreaded programs, if one thread waits for an I/O operation to complete, another thread can continue to run, enabling the program to continue running during I/O blocking.
The simplicity of 1.2.2 Modeling
If you include only one type of task in your program, it is easier to write than a program that contains many different types of tasks, with fewer errors and easier testing. If you assign a dedicated thread to each type of task in the model, you can create an illusion of serial execution, separating the execution logic of the program from the details of the scheduling mechanism, the alternating operations, asynchronous I/O, and resource waits. By using threads, complex and asynchronous workflows can be further decomposed into a set of simple and synchronized workflows, each running in a separate thread and interacting at a specific synchronization location.
Simplified processing of 1.2.3 asynchronous events
When a server application accepts a socket connection request from multiple remote clients, it is less difficult to develop such programs if each connection is assigned its own thread and uses synchronous I/O.
If an application performs a read operation on the socket and no data arrives at this time, the read will block until the data arrives. In a single-threaded application, this not only means pausing during the process of the request, but also means that the processing of all requests will be halted during the time the thread is blocked. To avoid this problem, single-threaded server applications must use non-blocking I/O, which is much more complex than synchronous I/O and prone to error. However, if each request has its own processing thread, the blocking that occurs when a request is processed will not affect the processing of the other requests.
Therefore, the operating system provides a number of efficient ways to implement multi-channel I/O, such as: Unix's Select and poll system calls, to invoke these methods, the Java class Library needs to obtain a set of non-blocking I/O package (Java.nio). Non-blocking I/O has its own advantages, but if the operating system is better able to support threads, there will be less use of non-blocking I/O.
1.3 Thread-risk1.3.1 Security Issues
Thread safety can be very complex, and in the absence of sufficient synchronization, the order in which operations are performed in multiple threads is unpredictable and can even produce strange results.
Because multiple threads share the same memory address space and run concurrently, they may be able to access or modify variables that other threads are using.
Introduction to the 1th Chapter