Objects provide a way to divide a program into independent sections. Often, you also need to turn a program into separate, independently running subtasks.
Each of these independent subtasks is called a thread, and you program as if each thread runs by itself and has the CPU to itself. Some underlying mechanism is actually dividing up the CPU time for you, but in general, you don’t have to think about it, which makes programming with multiple threads a much easier task.
A process is a self-contained running program with its own address space. A multitasking operating system is capable of running more than one process (program) at a time, while making it look like each one is chugging along on its own, by periodically switching the CPU from one task to another. A thread is a single sequential flow of control within a process. A single process can thus have multiple concurrently executing threads.
There are many possible uses for multithreading, but in general, you’ll have some part of your program tied to a particular event or resource, and you don’t want that to hold up the rest of your program. So, you create a thread associated with that event or resource and let it run independently of the main program.
Concurrent programming is like stepping into an entirely new world and learning a new programming language, or at least a new set of language concepts. With the appearance of thread support in most microcomputer operating systems, extensions for threads have also been appearing in programming languages or libraries. In all cases, thread programming:
- Seems mysterious and requires a shift in the way you think about
- Looks similar to thread support in other languages, so when you understand
threads, you understand a common tongue
And although support for threads can make Java a more complicated language, this isn’t entirely the fault of Java—threads are tricky.
Understanding concurrent programming is on the same order of difficulty as understanding polymorphism. If you apply some effort, you can fathom the basic mechanism, but it generally takes deep study and understanding in order to develop a true grasp of the subject. The goal of this chapter is to give you a solid foundation in the basics of concurrency so that you can understand the concepts and write reasonable multithreaded programs. Be aware that you can easily become overconfident, so if you are writing anything complex, you will need to study dedicated books on the topic.
One of the most compelling reasons for concurrency is to produce a responsive user interface. Consider a program that performs some CPU-intensive operation and thus ends up ignoring user input and being unresponsive. The basic problem is that the program needs to continue performing its operations, and at the same time it needs to return control to the user interface so that the program can respond to the user. If you have a “quit” button, you don’t want to be forced to poll it in every piece of code you write in your program, and yet you want the quit button to be responsive, as if you were checking it regularly.
A conventional method cannot continue performing its operations and at the same time return control to the rest of the program. In fact, this sounds like an impossible thing to accomplish, as if the CPU must be in two places at once, but this is precisely the illusion that concurrency provides.
Concurrency can also be used to optimize throughput. For example, you might be able to do important work while you’re stuck waiting for input to arrive on an I/O port. Without threading, the only reasonable solution is to poll the I/O port, which is awkward and can be difficult.
If you have a multiprocessor machine, multiple threads may be distributed across multiple processors, which can dramatically improve throughput. This is often the case with powerful multiprocessor web servers, which can distribute large numbers of user requests across CPUs in a program that allocates one thread per request.
One thing to keep in mind is that a program with many threads must be able to run on a single-CPU machine. Therefore, it must also be possible to write the same program without using any threads. However, multithreading provides a very important organizational benefit, so that the design of your program can be greatly simplified. Some types of problems, such as simulation—a video game, for example—are very difficult to solve without support for concurrency.
The threading model is a programming convenience to simplify juggling several operations at the same time within a single program. With threads, the CPU will pop around and give each thread some of its time. Each thread has the consciousness of constantly having the CPU to itself, but the CPU’s time is actually sliced between all the threads. The exception to this is if your program is running on multiple CPUs, but one of the great things about threading is that you are abstracted away from this layer, so your code does not need to know whether it is actually running on a single CPU or many. Thus, threads are a way to create transparently scalable programs—if a program is running too slowly, it can easily be made faster by adding CPUs to your computer. Multitasking and multithreading tend to be the most reasonable ways to utilize multiprocessor systems.
Threading can reduce computing efficiency somewhat in single-CPU machines, but the net improvement in program design, resource balancing, and user convenience is often quite valuable. In general, threads enable you to create a more loosely-coupled design; otherwise, parts of your code would be forced to pay explicit attention to tasks that would normally be handled by threads.