1 source
- Source: "Detailed Multithreading and Architecture Design of Java High Concurrency Programming", written by Wang Wenjun
- Chapters: Chapters 1, 2, and 3
This article is a compilation of notes from the first three chapters.
2 Overview
This article mainly describes the life cycle of the thread, the construction method of the Thread
class and the commonly used API
, and finally introduces the closing method of the thread.
3 Thread life cycle
3.1 The five stages
The thread life cycle can be divided into five phases:
NEW
RUNNABLE
RUNNING
BLOCKED
TERMINATED
3.2 NEW
When a Thread
object is created with new
, the thread is not started with start()
, and the thread is in the NEW
state. To be precise, it's just the state of the Thread
object, which is a normal Java
object. At this point, you can enter the RUNNABLE
state through the start()
method.
3.3 RUNNABLE
To enter the RUNNABLE
state, the start()
method must be called, thus creating a thread in JVM
. However, once a thread is created, it cannot be executed immediately. Whether the thread is executed or not needs to be scheduled by CPU
, that is to say, it is in an executable state at this time and qualified for execution, but it is not actually executed, but is Waiting to be scheduled.
RUNNABLE
state can only terminate unexpectedly or enter the RUNNING
state.
3.4 RUNNING
Once CPU
selects a thread from the task executable queue by polling or other means, the thread can be executed at this time, that is, it is in the RUNNING
state. In this state, the possible state transitions are as follows:
- Enter
TERMINATED
: For example, call the deprecatedstop()
method - Enter
BLOCKED
: For example, thesleep()
/wait()
method is called, or a blocking operation is performed (acquisition of lock resources, diskIO
, etc.) - Enter
RUNNABLE
:CPU
time slice arrives, or the thread actively callsyield()
3.5 BLOCKED
That is, the blocking state, there are many reasons for entering the blocking state, the common ones are as follows:
- Disk
IO
- network operation
- Entering a blocking operation to acquire a lock
When in state BLOCKED
, possible state transitions are as follows:
- Enter
TERMINATED
: such as calling the deprecatedstop()
, orJVM
unexpectedly died - Enter
RUNNABLE
: such as the end of hibernation, being woken up bynotify()
/nofityAll()
, acquiring a lock, interrupting the blocking process byinterrupt()
, etc.
3.6 TERMINATED
TERMINATED
is the final state of the thread. After entering this state, it means that the life cycle of the thread ends. For example, it will enter this state in the following cases:
- Thread run ends normally
- Thread running error ended unexpectedly
JVM
Unexpected crash causing all threads to forcibly end
4 Thread
construction method
4.1 Constructor
There are eight construction methods of Thread
, which are classified according to the naming method. The construction methods using the default naming are as follows:
Thread()
Thread(Runnable target)
Thread(ThreadGroup group,Runnable target)
The constructor for named threads is as follows:
Thread(String name)
Thread(Runnable target,Strintg name)
Thread(ThreadGroup group,String name)
Thread(ThreadGroup group,Runnable target,String name)
Thread(ThreadGroup group,Runnable target,String name,long stackSize)
But in fact all constructors end up calling the following private constructors:
private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);
In the default naming construction method, you can see in the source code that the default naming is actually the command of Thread-X
(X is a number):
public Thread() {
this((ThreadGroup)null, (Runnable)null, "Thread-" + nextThreadNum(), 0L);
}
public Thread(Runnable target) {
this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L);
}
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
And in the named constructor is the custom name.
In addition, if you want to modify the name of the thread, you can call the setName()
method, but it should be noted that the thread in the NEW
state can only be modified.
4.2 Parent-child relationship of threads
All constructors of Thread
call the following methods:
private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);
One of the source code snippets is as follows:
if (name == null) {
throw new NullPointerException("name cannot be null");
} else {
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
}
You can see that there is currently a local variable called parent
, and the assignment is currentThread()
, and currentThread()
is a native
method. Because the initial state when a thread is created is NEW
, so currentThread()
represents the thread that created its own thread, that is, the conclusion is as follows:
- The creation of one thread must be done by another thread
- The parent thread of the created thread is the thread that created it
That is, the thread created by itself, the parent thread is the main
thread, and the main
thread is created by JVM
.
In addition, several of the construction methods of Thread
have the ThreadGroup
parameter, which specifies which ThreadGroup
the thread is located in. If a thread is created without specifying ThreadGroup
, it will be the same ThreadGroup
as the parent thread. main
where the ThreadGroup
thread resides is called main
.
4.3 About stackSize
There is a stackSize
parameter in the Thread
construction method, which specifies the number of bytes in the address space of the thread stack allocated by JVM
, which is highly dependent on the platform. On some platforms:
- Setting a larger value: It can increase the recursion depth of calls within the thread and reduce the probability of
StackOverflowError
appearing - Setting a lower value: it can increase the number of threads created and delay the appearance of
OutOfMemoryError
However, on some platforms this parameter has no effect. Also, if set to 0 it will not have any effect.
5 Thread API
5.1 sleep()
sleep()
has two overloaded methods:
sleep(long mills)
sleep(long mills,int nanos)
But after JDK1.5
, TimeUnit
was introduced, which provides a good encapsulation for the sleep()
method. It is recommended to use TimeUnit.XXXX.sleep()
instead of Thread.sleep()
:
TimeUnit.SECONDS.sleep(1);
TimeUnit.MINUTES.sleep(3);
5.2 yield()
yield()
is a heuristic method that reminds CPU
scheduler that the current thread will voluntarily give up resources. If CPU
resource is not tight, this reminder will be ignored. Calling the yield()
method will change the current thread from RUNNING
to RUNNABLE
state.
Regarding the difference between yield()
and sleep()
, the differences are as follows:
sleep()
will cause the current thread to suspend for the specified time, without the consumption of theCPU
time sliceyield()
is just a hint to theCPU
scheduler. If theCPU
scheduler does not ignore this hint, it will cause a thread context switchsleep()
will cause the thread to block briefly, releasingCPU
resources within a given time- If
yield()
takes effect,yield()
will make the state fromRUNNING
enter the state ofRUNNABLE
sleep()
will almost 100% complete sleep for a given time, butyield()
's prompt may not guarantee- One thread calling
sleep()
and another thread callinginterrupt()
will catch the interrupt signal, butyield
will not
5.3 setPriority()
5.3.1 Introduction to Priority
Similar to a process, a thread also has its own priority. In theory, a thread with a higher priority will have the opportunity to be scheduled first, but this is not the case. Setting the priority is similar to yield()
, and it is also a reminder operation:
- For
root
users, the operating system will be reminded of the priority you want to set, otherwise it will be ignored - If
CPU
is busy, setting the priority may get moreCPU
time slices, but the priority level when idle has almost no effect
Therefore, setting the priority is only to allow a thread to get as many execution opportunities as possible to a large extent, that is, to let the thread itself be scheduled by the operating system as much as possible, instead of setting a high priority, it must run first, or priority Threads with a higher priority must run in preference to threads with a lower priority.
5.3.2 Priority source code analysis
Set the priority and directly call setPriority()
. The source code of OpenJDK 11
is as follows:
public final void setPriority(int newPriority) {
this.checkAccess();
if (newPriority <= 10 && newPriority >= 1) {
ThreadGroup g;
if ((g = this.getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
this.setPriority0(this.priority = newPriority);
}
} else {
throw new IllegalArgumentException();
}
}
It can be seen that the priority is between [1,10]
and cannot be set to a priority greater than the current ThreadGroup
. Finally, the priority is set by native
method setPriority0
.
Under normal circumstances, the priority level of the thread is not set. By default, the priority of the thread is 5, because the priority of the main
thread is 5, and main
is the parent process of all threads, so the priority of the thread is by default. Level is also 5.
5.4 interrupt()
interrupt()
is an important API
, and there are three API
thread interruptions as follows:
void interrupt()
boolean isInterrupted()
static boolean interrupted()
The following will analyze them one by one.
5.4.1 interrupt()
Some method calls will cause the current thread to enter a blocking state, such as:
Object.wait()
Thread.sleep()
Thread.join()
Selector.wakeup()
Calling interrupt()
can interrupt the blocking. Interrupting the blocking does not mean the end of the thread's life cycle, but only interrupts the blocking state of the current thread. Once interrupted in the blocking state, an exception of InterruptedException
will be thrown. This exception is like a signal to notify the current thread that the interrupt has been interrupted. The example is as follows:
public static void main(String[] args) throws InterruptedException{
Thread thread = new Thread(()->{
try{
TimeUnit.SECONDS.sleep(10);
}catch (InterruptedException e){
System.out.println("Thread is interrupted.");
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
Will output the information that the thread was interrupted.
5.4.2 isInterrupted()
isInterrupted()
can judge whether the current thread is interrupted, it is only a judgment of the interrupt()
logo, and will not affect any changes in the logo (because when interrupt flag
interrupt()
be set). The example is as follows:
public static void main(String[] args) throws InterruptedException{
Thread thread = new Thread(()->{
while (true){}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("Thread is interrupted :"+thread.isInterrupted());
thread.interrupt();
System.out.println("Thread is interrupted :"+thread.isInterrupted());
}
The output is:
Thread is interrupted :false
Thread is interrupted :true
Another example is as follows:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("In catch block thread is interrupted :" + isInterrupted());
}
}
}
};
thread.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("Thread is interrupted :" + thread.isInterrupted());
thread.interrupt();
TimeUnit.SECONDS.sleep(1);
System.out.println("Thread is interrupted :" + thread.isInterrupted());
}
Output result:
Thread is interrupted :false
In catch block thread is interrupted :false
Thread is interrupted :false
At the beginning, the thread was not interrupted, and the result was false
. After calling the interrupt method, an exception (signal) was caught in the loop body. At this time, Thread
itself will erase interrupt
flag and reset the flag, so the output result after the exception is caught is also false
.
5.4.3 interrupted()
This is a static method. Calling this method will erase the thread's interrupt
identifier. It should be noted that if the current thread is interrupted:
- The first call to
interrupted()
will returntrue
and immediately eraseinterrupt
flag - The second and subsequent calls will always return
false
unless the thread is interrupted again in the meantime
Examples are as follows:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
System.out.println(Thread.interrupted());
}
}
};
thread.setDaemon(true);
thread.start();
TimeUnit.MILLISECONDS.sleep(2);
thread.interrupt();
}
Output (truncated part):
false
false
false
true
false
false
false
It can be seen that there is a true
, that is, interrupted()
judges that it is interrupted, and the interrupt flag will be erased immediately, and only this time it returns true
, followed by false
.
About the difference between interrupted()
and isInterrupted()
, you can know from the source code ( OpenJDK 11
):
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return this.isInterrupted(false);
}
@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean var1);
In fact, both call the same native
method, where the boolean variable indicates whether to erase the thread's interrupt
identity:
true
means you want to erase,interrupted()
does itfalse
means you don't want to erase,isInterrupted()
does it
5.5 join()
5.5.1 Introduction join()
join()
, like sleep()
, is an interruptible method. If other threads execute the interrupt
operation on the current thread, the interrupt signal will also be captured, and the interrupt
identifier of the thread will be join()
API
void join()
void join(long millis,int nanos)
void join(long mills)
5.5.2 Examples
A simple example is as follows:
public class Main {
public static void main(String[] args) throws InterruptedException {
List<Thread> threads = IntStream.range(1,3).mapToObj(Main::create).collect(Collectors.toList());
threads.forEach(Thread::start);
for (Thread thread:threads){
thread.join();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" # "+i);
shortSleep();
}
}
private static Thread create(int seq){
return new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" # "+i);
shortSleep();
}
},String.valueOf(seq));
}
private static void shortSleep(){
try{
TimeUnit.MILLISECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
The output is intercepted as follows:
2 # 8
1 # 8
2 # 9
1 # 9
main # 0
main # 1
main # 2
main # 3
main # 4
Thread 1 and thread 2 execute alternately, and the main
thread will wait until thread 1 and thread 2 are executed before executing.
6 threads close
There is an obsolete method Thread
in stop
, which can be used to close a thread, but the problem is that the lock of monitor
may not be released, so it is not recommended to use this method to close a thread. Thread shutdown can be divided into three categories:
- graceful shutdown
- abnormal exit
- suspended animation
6.1 Graceful shutdown
6.1.1 Normal end
After the thread finishes running, it will exit normally, which is the most common situation.
6.1.2 Catch the signal to close the thread
To close the thread by catching the interrupt signal, the example is as follows:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("work...");
while(!isInterrupted()){
}
System.out.println("exit...");
}
};
t.start();
TimeUnit.SECONDS.sleep(5);
System.out.println("System will be shutdown.");
t.interrupt();
}
Always check whether the interrupt
flag is set to true
, and set it to true
to jump out of the loop. Another way is to use sleep()
:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("work...");
while(true){
try{
TimeUnit.MILLISECONDS.sleep(1);
}catch (InterruptedException e){
break;
}
}
System.out.println("exit...");
}
};
t.start();
TimeUnit.SECONDS.sleep(5);
System.out.println("System will be shutdown.");
t.interrupt();
}
6.1.3 volatile
Since the interrupt
identifier is likely to be erased, or interrupt()
method will not be called, another method is to use volatile
decorate a boolean variable and continue to loop to judge:
public class Main {
static class MyTask extends Thread{
private volatile boolean closed = false;
@Override
public void run() {
System.out.println("work...");
while (!closed && !isInterrupted()){
}
System.out.println("exit...");
}
public void close(){
this.closed = true;
this.interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
MyTask t = new MyTask();
t.start();
TimeUnit.SECONDS.sleep(5);
System.out.println("System will be shutdown.");
t.close();
}
}
6.2 Abnormal exit
The checked
exception is not allowed to be thrown in the thread execution unit. If the checked
exception needs to be caught during the thread running process and it is necessary to judge whether it is still necessary to run, the checked
exception can be encapsulated as the unchecked
exception, such as RuntimeException
, and thrown to end the thread life cycle.
6.3 Suspended Death
The so-called suspended animation means that although the thread exists, it does not have any external manifestation, such as:
- no log output
- do not do any work
Wait, although the thread exists at this time, it looks like it is dead. In fact, it is not dead. In this case, it is most likely because the thread is blocked, or two threads are competing for resources and died. Lock.
This situation needs to be judged by some external tools, such as VisualVM
, jconsole
, etc., to find out the thread in question and the current state, and determine which method caused the blocking.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。