Today I met some multithreading problems. Many Java syntax become very confusing in a multithreading environment. So I created a mini Java program, and wrote this study note to clarify the unclear methods. This blog post will go through the following steps:
- Source code and execution
- Understand
Thread#join()
- Understand logic sharing
- Understand variable sharing
Source Code and Execution
My mini Java program:
Execution:
$ java -Djava.util.logging.SimpleFormatter.format='%1$tS.%1$tLs %2$s: %5$s%6$s%n' App
01.081s App main: [main] Main thread started.
01.111s App$Slave run: [Slave] Slave is running
01.315s App$Slave run: [Slave] Slave is running
01.516s App$Slave run: [Slave] Slave is running
01.719s App$Slave run: [Slave] Slave is running
01.925s App$Slave run: [Slave] Slave is running
02.130s App$Slave run: [Slave] Slave is running
02.334s App$Slave run: [Slave] Slave is running
02.535s App$Slave run: [Slave] Slave is running
02.737s App$Slave run: [Slave] Slave is running
02.940s App$Slave run: [Slave] Slave is running
03.116s App$Master run: [Master] Closing slave...
03.116s App$Slave close: [Master] Closed in 1 second.
03.143s App$Slave run: [Slave] Slave is running
03.350s App$Slave run: [Slave] Slave is running
03.554s App$Slave run: [Slave] Slave is running
03.759s App$Slave run: [Slave] Slave is running
03.962s App$Slave run: [Slave] Slave is running
04.122s App$Slave close: [Master] Slave is closed.
04.122s App$Master run: [Master] Slave is closed.
04.123s App main: [main] Main thread finished.
Thread.join()
This section explains how to use Thread.join()
.
In the mini program, there’s a line which uses method Thread#join()
:
It means that the main thread waits for thread Master to die. The waiting will last forever without timeout. This can be verified by the log trace, too—main thread did not quit until master is finished:
01.081s App main: [main] Main thread started.
...
04.122s App$Master run: [Master] Slave is closed.
04.123s App main: [main] Main thread finished.
This can be used for any scenario where a thread needs to wait for another thread’s termination, e.g. as a shutdown hook.
Logic Sharing
This section explains the logic sharing.
In the mini program, master closes the slave by invoking method Slave#close()
.
But, in which thread this logic is invoked in reality? When seeing the following
code block, it’s confusing and unclear how it works:
To clarify the situation, let’s check the log trace. The class and method name defined after the log level is the location of the logic; the value in the square bracket […] is the name of the thread:
03.116s App$Master run: [Master] Closing slave...
03.116s App$Slave close: [Master] Closed in 1 second.
03.143s App$Slave run: [Slave] Slave is running
03.350s App$Slave run: [Slave] Slave is running
03.554s App$Slave run: [Slave] Slave is running
03.759s App$Slave run: [Slave] Slave is running
03.962s App$Slave run: [Slave] Slave is running
04.122s App$Slave close: [Master] Slave is closed.
Even though object slave
is submit to thread Slave
, thread Master
is the
one which closes the slave. In other words, thread Master
executed the logic
of Slave#close()
.
This is very interesting. It means that each thread runs its own logic, and there’s no way to do cross-thread control. The only thing a thread can do is to “ask” another thread to perform action: how the other thread acts depends completely its own implementation, it might or might not cooperate correctly. We’ll see that in the next section.
Variable Sharing
In order the make threads cooperate together, we can use shared variables.
There’re multiple ways to share variables between 2 threads. You can use
atomic variables (classes in Java concurrent package java.util.concurrent
),
use volatile variables, or use synchronization around the code.
In my mini program, I used volatile
.
If you want to know more about these approaches, check:
The important part is: one thread cannot control another. It sends signal (2) or
shares variable (1) to another, and let that thread controls itself. In our case,
slave watches the variable running
, and quit when it turns to false:
The slave thread will stop if:
- Running becomes false
- A thread interruption request is received (exception raised)
Conclusion
In Java, class is an encapsulation of logic and variables. Threads, on the other
hand, are logic executors. When thread A executes task.doSth()
, you should not
interpret it as “Task task
does something”, but “Thread A does
something”. In OOP, object does not describe the real world. Thread do. When
thinking in this way, it helps to understand multithreading. After all, hope you
enjoy this post, see you the next time!