The labs on this site need JavaScript to run. The same content is written out below.
Learn › Java › Threads and concurrency
Processes and threads
Two ways to add more workers
When you run a program, one process is created, and the threads inside it are what actually execute the code. This lab runs three tasks that take 0.3 seconds each in three ways: on one thread, on three threads in one process, and in three processes. The ways differ in whether memory is shared and in what happens to the rest when one of them dies.
Checked against real execution · Checked by running the 9 combinations on JDK 21
Concepts in this lab
- Processes and threads
- A process is a running program, and the operating system gives it its own independent memory space. A thread is a flow of execution that does the actual work inside a process. The threads of one process share memory (heap, code, and data), and each has only its own stack. This makes it easy for threads to exchange data, but it also causes problems when they touch the same data at the same time.
- Inter-process communication (IPC)
- This is how processes that each have their own independent memory exchange data. The methods include pipes, message queues, shared memory, and sockets. Communication between server programs mostly uses HTTP or a message broker, which run on top of sockets.
- Run-time data areas
- These are the memory areas that the JVM uses while it runs a program. The areas that each thread has for itself are the pc register, the Java Virtual Machine stack, and the native method stack, and the areas shared by all threads are the heap and the method area. They are the basis for understanding which data is shared between threads.
- Context switching
- This is the process in which the CPU stops the thread (or process) it is running and switches to run another one. The state of the current work must be saved and the state of the next work must be loaded, and the switch itself takes time. When there are far more threads than CPU cores, the time spent on switching grows relative to the time spent on actual work.
Try these
- Default settings — 3 threads. They finish in 0.3 seconds and increment the same variable together.
- One thread, one task after another — The times of the three tasks simply add up.
- 3 processes — They finish at the same time, but each has its own separate variable.
- One thread dies from an exception — Only that thread ends, and the others continue.
- An error that kills the process — When the 3 threads are in one process, they die together.
- The same error, in separate processes — Only that process dies, and the others are unharmed.
Running with the default settings
- The program is run, which creates 1 process. The operating system gives this process memory, and there is one counter variable in it. Inside the process, 3 threads are created. Each thread has only its own flow of execution (its stack), and the threads share the memory of the process.
- The 3 threads each take one task and start at the same time. They do not wait for one another.
- The remaining threads finish their tasks and increment the same counter. counter is 3. Several threads share one variable, so the problems of touching it at the same time must be prevented separately (the next concept, race conditions).
Result of this run
- Threads share the memory of the same process. They are lightweight to create and can exchange data easily, but touching the same data at the same time causes problems, and when the process dies, all the threads die with it. A web server (Tomcat) that uses a thread for each request works this way.
Code
static int counter = 0; // a variable in the memory of the process
static void work(int id) {
try { Thread.sleep(300); } catch (InterruptedException e) { return; } // a task that takes 0.3 seconds
counter++;
}
// 3 threads in the same process
for (int i = 1; i <= 3; i++) {
int id = i;
new Thread(() -> work(id)).start();
}
Assumptions of this model
The 9 combinations were checked by actually running them on JDK 21 on this PC. With 3 threads it took 0.3 seconds and with 1 thread 0.9 seconds, and the threads incremented the same counter up to 3, whereas the 3 processes each had 1. An exception ended only the thread it was thrown in, System.exit ended every thread in the same process, and with separate processes, every process other than the one that died terminated normally. The time and memory it takes to create a process are not shown on the screen (in reality they are much larger, because a new JVM is started each time).
Previous lab: Parallel streams and the common pool · Next lab: Creating threads and thread states