1 source
- Source: Java high Concurrency programming in detail multithreading and architecture design, by Wang Wenjun
- Chapters: Chapters 1, 2 and 3
This article is the notes of the first three chapters.
2 an overview
This article mainly describes the Thread life cycle, the construction method of Thread class and common API, and finally introduces the Thread closing method.
3 Thread life cycle
3.1 Five Stages
The thread lifecycle can be divided into five phases:
NEW
RUNNABLE
RUNNING
BLOCKED
TERMINATED
3.2 NEW
When a Thread object is created with new, but the Thread is not started with start(), 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. You can use the start() method to enter the RUNNABLE state.
3.3 RUNNABLE
To enter the RUNNABLE state, you must call the start() method, which creates a thread in the JVM. However, once a thread is created, it cannot be executed immediately. Whether a thread executes or not depends on CPU scheduling. In other words, it is in an executable state and qualified for execution, but it is waiting to be scheduled instead of actually executing.
The RUNNABLE state can only end unexpectedly or enter the RUNNING state.
3.4 RUNNING
Once the CPU has selected a thread from the task executable queue by polling or other means, the thread can only be executed, that is, in the RUNNING state. In this state, the following state transitions may occur:
- Enter the
TERMINATED
: For example, the call is no longer recommendedstop()
methods - Enter the
BLOCKED
: For example, calledsleep()
/wait()
Method, or perform some blocking operation (get a lock resource, diskIO
Etc.) - Enter the
RUNNABLE
:CPU
The time slice is up, or the thread is actively calledyield()
3.5 BLOCKED
This is the blocking state. There are many reasons for entering the blocking state, including the following:
- disk
IO
- Network operating
- To enter a blocking operation in order to acquire a lock
When BLOCKED, the following state transitions can occur:
- Enter the
TERMINATED
: For example, calling an unrecommendedstop()
Or,JVM
Accidental death - Enter the
RUNNABLE
: Such as the end of hibernation, bynotify()
/nofityAll()
Wake up, acquire a lock, block the process byinterrupt()
Interrupt etc.
3.6 TERMINATED
TERMINATED is the final state of a thread. Entry into TERMINATED state indicates the end of the thread’s life cycle, for example, in the following cases:
- The thread ends properly
- A thread running error ended unexpectedly
JVM
Unexpected crash, forcing all threads to terminate
4 Thread
A constructor
4.1 Construction method
There are altogether eight constructors of Thread, which are classified according to their naming methods. The constructors using the default name are as follows:
Thread()
Thread(Runnable target)
Thread(ThreadGroup group,Runnable target)
A named thread is constructed 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 constructor:
private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);
Copy the code
In the default naming constructor, you can see in the source code that the default naming is actually the command for Thread-x (X is a number) :
public Thread(a) {
this((ThreadGroup)null, (Runnable)null."Thread-" + nextThreadNum(), 0L);
}
public Thread(Runnable target) {
this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L);
}
private static synchronized int nextThreadNum(a) {
return threadInitNumber++;
}
Copy the code
The name constructor is a custom name.
Also, if you want to change the name of a thread, you can call setName(), but note that only threads in the NEW state can change the name.
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);
Copy the code
Here’s a snippet of the source code:
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(); }}}Copy the code
You can see that there is currently a local variable called parent and it’s assigned to currentThread(), which is a native method. Since the initial state of a thread is NEW, currentThread() represents the thread that created its own thread, i.e., the following conclusion:
- The creation of one thread must be done by another thread
- The parent of the created thread is the thread that created it
The parent thread is the main thread, which is created by the JVM.
In addition, several of the Thread constructors have the ThreadGroup parameter, which specifies which ThreadGroup the Thread is in. If a Thread is created without a specified ThreadGroup, it will have the same ThreadGroup as the parent Thread. The ThreadGroup of the main thread is called main.
4.3 aboutstackSize
The Thread constructor has a stackSize parameter that specifies the number of bytes in the address space that the JVM allocates to the Thread stack. It is platform dependent, on some platforms:
- Set to a larger value: This can increase or decrease the recursion depth of in-thread calls
StackOverflowError
Probability of occurrence - Set a low value: To increase the number of threads created, it can be delayed
OutOfMemoryError
Time of occurrence
However, on some platforms this parameter does not work at all. Also, setting it to 0 won’t do anything.
5 Thread API
5.1 sleep()
Sleep () has two overloaded methods:
sleep(long mills)
sleep(long mills,int nanos)
The sleep() method is wrapped in timeUnit.xxxx.sleep () instead of thread.sleep () :
TimeUnit.SECONDS.sleep(1);
TimeUnit.MINUTES.sleep(3);
Copy the code
5.2 yield()
Yield () is a heuristic that alerts the CPU scheduler to the voluntary abandonment of resources by the current thread and will be ignored if CPU resources are not strained. Calling yield() causes the current thread to change from RUNNING to RUNNABLE.
The difference between yield() and sleep() is as follows:
sleep()
Causes the current thread to pause for the specified timeCPU
Time slice consumptionyield()
Just forCPU
A hint from the scheduler ifCPU
The scheduler does not ignore this prompt, resulting in a thread context switchsleep()
Causes the thread to briefly block and be released within a given timeCPU
resources- if
yield()
To take effect,yield()
Make fromRUNNING
State entryRUNNABLE
state sleep()
Will do almost 100% of the sleep for a given time, butyield()
A hint is not necessarily a guarantee- One thread call
sleep()
And another thread callsinterrupt()
Will pick up an interrupt signal, andyield
Will not be
5.3 setPriority()
5.3.1 Priorities
Threads, like processes, have their own priorities. In theory, a thread with a higher priority has a chance to be scheduled first, but in practice this is not the case. Setting a priority is similar to yield() and is also a reminder operation:
- for
root
The user will alert the operating system to the desired priority level, otherwise it will be ignored - if
CPU
If you are busy, you may get more by setting prioritiesCPU
Time slice, but when idle, the priority level has almost no effect
Therefore, setting a priority is to a large extent to allow a thread to get as many execution opportunities as possible, that is, to allow the thread itself to be scheduled by the operating system as much as possible, rather than setting a higher priority must run first, or a higher priority thread must run first than a lower priority thread.
5.3.2 Priority source code analysis
To set the priority, call setPriority().
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 newIllegalArgumentException(); }}Copy the code
It can be seen that the priority is between [1,10] and cannot be set higher than the priority of the current ThreadGroup. Finally, set the priority through the native method setPriority0.
Normally, the priority level of a thread is not set. By default, the priority of a thread is 5, because the main thread has a priority of 5 and main is the parent of all threads, so it also has a priority of 5 by default.
5.4 interrupt()
Interrupt () is an important API. There are three apis for thread interrupts:
void interrupt()
boolean isInterrupted()
static boolean interrupted()
The following will analyze them one by one.
5.4.1 interrupt()
Some method calls cause the current thread to block, such as:
Object.wait()
Thread.sleep()
Thread.join()
Selector.wakeup()
Calling interrupt(), however, interrupts a block. Interrupting a block does not end the thread’s life cycle, but merely interrupts the blocking state of the current thread. Once interrupted while blocking, an InterruptedException is thrown, which acts as a signal to inform the current thread that it has been interrupted, as shown in the following example:
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();
}
Copy the code
Outputs a message that the thread was interrupted.
5.4.2 isInterrupted()
IsInterrupted () checks whether the current thread has been interrupted. This checks only for interrupt() and does not affect any changes to the interrupt() flag, as shown in the following example:
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());
}
Copy the code
The output is:
Thread is interrupted :false
Thread is interrupted :true
Copy the code
Here’s another example:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run(a) {
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());
}
Copy the code
Output result:
Thread is interrupted :false
In catch block thread is interrupted :false
Thread is interrupted :false
Copy the code
If the Thread is not interrupted at first, the result is false. If the interrupt method is called and an exception is caught in the loop, the Thread itself will erase the interrupt identifier and reset it, so the output is false.
5.4.3 interrupted()
This is a static method. Calling this method erases the interrupt mark on the thread. Note that if the current thread is interrupted:
- First call
interrupted()
Returns thetrue
“And wipe it off immediatelyinterrupt
logo - The second and subsequent calls 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(a) {
while (true) { System.out.println(Thread.interrupted()); }}}; thread.setDaemon(true);
thread.start();
TimeUnit.MILLISECONDS.sleep(2);
thread.interrupt();
}
Copy the code
Output (partial cut) :
false
false
false
true
false
false
false
Copy the code
If interrupted() is interrupted, the interrupt flag is immediately erased and true is returned for this only case.
The difference between interrupted() and isInterrupted() can be seen in the source code (OpenJDK 11) :
public static boolean interrupted(a) {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted(a) {
return this.isInterrupted(false);
}
@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean var1);
Copy the code
In fact, both call the same native method, where the Boolean variable indicates whether the thread’s interrupt identifier is erased:
true
I want to erase,interrupted()
That’s how you do itfalse
I don’t want to erase,isInterrupted()
That’s how you do it
5.5 join()
5.5.1 join()
Introduction to the
Join (), like sleep(), is a method that can interrupt. If other threads interrupt the current thread, they will catch the interrupt signal and erase the thread’s interrupt identifier. Join () provides three apis, as follows:
void join()
void join(long millis,int nanos)
void join(long mills)
5.5.2 example
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(a){
try{
TimeUnit.MILLISECONDS.sleep(2);
}catch(InterruptedException e){ e.printStackTrace(); }}}Copy the code
The output is captured as follows:
2 # 8
1 # 8
2 # 9
1 # 9
main # 0
main # 1
main # 2
main # 3
main # 4
Copy the code
Thread 1 and thread 2 execute alternately, while the main thread waits until thread 1 and thread 2 have finished executing.
6 Thread Closing
Thread has an outdated stop method that can be used to close a Thread, but the problem is that the monitor lock may not be released, so it is not recommended to close a Thread using this method. Thread closure can be divided into three categories:
- Normally closed
- Abnormal exit
- Feign death
6.1 Normal Shutdown
6.1.1 Normal End
When a thread finishes running, it exits normally, which is the most common case.
6.1.2 Catch a signal to close the thread
To close a thread by catching an interrupt signal, as shown in the following example:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run(a) {
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();
}
Copy the code
Always check to see if the interrupt flag is set to true, which breaks the loop. Another way is to use sleep() :
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run(a) {
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();
}
Copy the code
Also 6.1.3volatile
Since the interrupt identifier is likely to be erased or the interrupt() method will not be called, the alternative is to use volatile to modify a Boolean variable and iterate:
public class Main {
static class MyTask extends Thread{
private volatile boolean closed = false;
@Override
public void run(a) {
System.out.println("work...");
while(! closed && ! isInterrupted()){ } System.out.println("exit...");
}
public void close(a){
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(); }}Copy the code
6.2 Abnormal Exit
Unchecked exceptions are not allowed to be thrown in thread execution units. If you need to catch checked exceptions while a thread is running and determine whether it is still necessary to run, encapsulate them as unchecked exceptions, such as RuntimeException. Throws to end the thread’s life cycle.
6.3 feign death
Suspended animation is a thread that exists but has no outward appearance, such as:
- No log output
- You don’t do any homework
Wait, even though the thread exists, it looks like it’s dead, but in fact it’s not. This is most likely because the thread is blocked, or two threads are competing for resources and there is a deadlock.
This situation requires external tools such as VisualVM, JConsole, etc., to identify the thread in question and its current state, and to determine which method is blocking.