Multithreading
1. Related concepts
Concurrency and Parallelism
Parallel (parallel): Refers to multiple event tasks occurring at the same time (simultaneously).
Concurrency : Refers to two or more events occurring within the same tiny period of time .The concurrent execution of programs can make full use of CPU resources under limited conditions.
Single-core CPU: only concurrent
Multi-core CPU: parallel + concurrent
Threads and Processes
-
Program : In order to complete a certain task and function, choose a set of instructions written in a programming language.
-
Software : 1 or more applications + related materials and resource files constitute a software system.
-
A process is a description of the running process (creation-run-death) of a program. The system creates a process for each running program and allocates independent system resources, such as memory space, to the process.
-
Thread : A thread is an execution unit in a process, which is responsible for completing the task of executing the current program. There is at least one thread in a process. There can be multiple threads in a process, and this application program can also be called a multi-threaded program at this time. Multi-threading enables programs to execute concurrently and make full use of CPU resources.
Interview question : A process is the smallest unit of operating system scheduling and resource allocation, and a thread is the smallest unit of CPU scheduling. Different processes do not share memory. The cost of data exchange and communication between processes is high. Different threads share the memory of the same process. Of course, different threads also have their own independent memory space. For the method area, the memory of the same object in the heap can be shared between threads, but the local variables of the stack are always independent.
Advantages and application scenarios of multithreading
- The main advantage:
- Make full use of CPU idle time slices to complete user requests in the shortest possible time. That is to make the program respond faster.
- Application scenario:
- multitasking. When multiple users request the server, the server program can open multiple threads to process each user's request separately without affecting each other.
- Single large task processing. To download a large file, you can open multiple threads to download together, reducing the overall download time.
thread scheduling
Refers to how CPU resources are allocated to different threads. Two common thread scheduling methods:
-
time-sharing scheduling
All threads take turns to use the CPU, and each thread takes up CPU time evenly.
-
preemptive scheduling
Give priority to threads with high priority to use the CPU. If the threads have the same priority, one will be randomly selected (thread randomness). Java uses a preemptive scheduling method .
2. Thread creation and startup
Inherit the Thread class
Steps to create and start multithreading by inheriting the Thread class:
- Define the subclass of the Thread class, and rewrite the run() method of this class. The method body of the run() method represents the task that the thread needs to complete, so the run() method is called the thread execution body.
- Create an instance of the Thread subclass, that is, create a thread object
- Call the start() method of the thread object to start the thread
Notes on multi-threaded execution analysis :
-
Manually calling the run method is not the way to start a thread, it's just a normal method call.
-
After the start method starts the thread, the run method will be called and executed by the JVM.
-
Do not start the same thread repeatedly, otherwise an exception will be thrown
IllegalThreadStateException
-
Do not use Junit unit to test multi-threading, it is not supported, after the main thread ends, it will call to
System.exit()
exit the JVM directly;
Implement the Runnable interface
- Define the implementation class of the Runnable interface, and rewrite the run() method of the interface. The method body of the run() method is also the thread execution body of the thread.
- Create an instance of the Runnable implementation class, and use this instance as the target of Thread to create a Thread object, which is the real thread object.
- Call the start() method of the thread object to start the thread.
Comparison of two ways of creating threads
-
The Thread class itself also implements the Runnable interface. The run method comes from the Runnable interface, and the run method is also the actual thread task to be executed.
public class Thread implements Runnable { }
-
Because Java classes are single-inherited, the way of inheriting Thread has the limitation of single-inheritance, but it is simpler to use.
-
The way to implement the Runnable interface avoids the limitation of single inheritance, and can make multiple thread objectsShare a Runnable implementation class (thread task class) object, so as to facilitate the execution of multi-threaded tasksshare data.
Anonymous inner class object creation thread
Creating threads by means of anonymous inner class objects is not a new way of creating threads, but when the thread task only needs to be executed once, we don’t need to create thread classes separately, we can use anonymous objects:
new Thread("新的线程!"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}.start();
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":" + i);
}
}
}).start();
3. Thread class
Construction method
- public Thread() : Allocate a new thread object.
- public Thread(String name): Allocate a new thread object with the specified name.
- public Thread(Runnable target) : Allocates a new thread object with the specified target.
- public Thread(Runnable target, String name) : Allocate a new thread object with the specified target and specify the name.
Threads use the basic method
-
public void run() : The task to be performed by this thread is defined here.
-
public String getName() : Get the name of the current thread.
-
public static Thread currentThread() : Returns a reference to the currently executing thread object.
-
public final boolean isAlive(): Tests whether the thread is alive. Active if the thread has been started and has not yet terminated.
-
public final int getPriority() : returns thread priority
-
public final void setPriority(int newPriority): change the priority of the thread
- Each thread has a certain priority, and threads with higher priority will get more execution opportunities. By default, each thread has the same priority as the parent thread that created it. The Thread class provides the setPriority(int newPriority) and getPriority() method classes to set and get the priority of the thread, where the setPriority method requires an integer, and the range is between [1,10]. It is usually recommended to set the three priorities of the Thread class class constants:
- MAX_PRIORITY (10): highest priority
- MIN_PRIORITY (1): lowest priority
- NORM_PRIORITY (5): normal priority, by default the main thread has normal priority.
Common methods of thread control
-
public void start() : Causes this thread to start executing; the Java virtual machine calls the run method of this thread.
-
public static void sleep(long millis): Thread sleep, which makes the currently executing thread pause (temporarily stop executing) for the specified number of milliseconds.
-
public static void yield(): Thread politeness, yield just makes the current thread temporarily lose the right to execute, let the system's thread scheduler reschedule, hoping that other threads with the same or higher priority as the current thread can get the chance to execute, but this There is no guarantee. It is entirely possible that when a thread calls the yield method to pause, the thread scheduler will schedule it to re-execute.
-
void join() : Join a thread, add a new thread to the current thread, wait for the joined thread to terminate before continuing to execute the current thread.
void join(long millis): waits for this thread to terminate for up to millis millis milliseconds. If the millis time is up, there will be no more waiting.
void join(long millis, int nanos) : Wait for the thread to terminate for up to millis millis + nanos nanoseconds.
-
public final void stop(): Forces the thread to stop executing. This method is unsafe, deprecated, and should not be used.
- Calling the stop() method will immediately stop all remaining work in the run() method, including those in the catch or finally statement, and throw a ThreadDeath exception (usually this exception does not require an explicit capture), so it may cause some The cleaning work cannot be completed, such as closing files, databases, etc.
- Calling the stop() method will immediately release all locks held by the thread, resulting in unsynchronized data and data inconsistency.
-
public void interrupt(): Interrupting the thread actually marks the thread as an interrupt, and does not actually stop the thread from executing.
-
public static boolean interrupted(): Check the interrupted status of the thread, calling this method will clear the interrupted status (flag).
-
public boolean isInterrupted(): Check thread interrupted status, will not clear interrupted status (flag)
-
public void setDaemon(boolean on): Set the thread as a daemon thread. It must be set before the thread starts, otherwise
IllegalThreadStateException
an exception will be reported.- The daemon thread mainly serves other threads. When there is no non-daemon thread executing in the program, the daemon thread will also terminate execution. The JVM garbage collector is also a daemon thread.
-
public boolean isDaemon(): Check whether the current thread is a daemon thread.
-
The role of volatile is to ensure that some instructions will not be omitted due to compiler optimization. The volatile variable means that the variable may be changed unexpectedly. Carefully re-read the value of this variable every time instead of using save A backup in the register, so that the compiler will not assume the value of this variable.
thread life cycle
Five thread states of traditional threading model
The six thread states defined by JDK define an enumeration class inside the class to describe the six states of the thread
:java.lang.Thread
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
Description: When returning from WAITING
or to the state, if it is found that the current thread has not obtained the monitor lock, it will immediately transfer to the state.TIMED_WAITING
Runnable
BLOCKED
thread safety
When we use multiple threads to access the same resource (it can be the same variable, the same file, the same record, etc.), but if there are read and write operations on the resource in multiple threads, there will be data inconsistency problems before and after , which is thread safety issue.
Thread safety issues lead to
- Local variables cannot be shared : local variables are independent each time the method is called, so the data of run() of each thread is independent, not shared data.
- Instance variables of different objects are not shared : instance variables of different instance objects are independent.
- static variables are shared
- Instance variables of the same object are shared
Summary: Thread safety issues arise because of the following conditions
- multi-threaded execution
- share data
- Multiple statements operate on shared data
Solution to thread safety problem
Synchronization method : The synchronized keyword directly modifies the method, indicating that only one thread can enter this method at the same time, and other threads are waiting outside.
public synchronized void method(){
可能会产生线程安全问题的代码
}
Synchronized code block : The synchronized keyword can be used in front of a certain block, indicating that only the resources of this block are mutually exclusive.
synchronized(同步锁){
需要同步操作的代码
}
Lock object selection
preferred thisfollowed byclass.classcan also be" "
empty string object
Synchronization lock object:
- Lock objects can be of any type.
- Multiple thread objects use the same lock.
The lock object of the synchronized code block
- In the static code block: use the Class object of the current class
- In non-static code blocks: it is customary to consider this first, but pay attention to whether the same this
The scope of the lock is too small: it cannot solve security problems, and all statements that operate on shared resources must be synchronized.
The scope of the lock is too large: because once a thread grabs the lock, other threads can only wait, so the scope is too large, the efficiency will be reduced, and CPU resources cannot be used reasonably.
Thread Safety Issues of Singleton Design Pattern
1. Hungry Chinese style has no thread safety issues
Hungry Chinese style: create objects as soon as you come up
2. Lazy-style thread safety issues
public class Singleton {
private static Singleton ourInstance;
public static Singleton getInstance() {
//一旦创建了对象,之后再次获取对象,都不会再进入同步代码块,提升效率
if (ourInstance == null) {
//同步锁,锁住判断语句与创建对象并赋值的语句
synchronized (Singleton.class) {
if (ourInstance == null) {
ourInstance = new Singleton();
}
}
}
return ourInstance;
}
private Singleton() {
}
}
Waiting for wake-up mechanism
When a thread meets a certain condition, it enters the waiting state ( wait() / wait(time) ), waits for other threads to execute their specified code and then wakes it up ( notify() ); or you can specify the wait time , Wait for the time to wake up automatically; when there are multiple threads waiting, if necessary, you can use notifyAll() to wake up all waiting threads. wait/notify is a coordination mechanism between threads.
- wait: The thread is no longer active, no longer participates in scheduling, and enters the wait set, so it will not waste CPU resources and will not compete for locks. At this time, the thread state is WAITING or TIMED_WAITING. It also waits for other threads to perform a special action , that is, " notify " or when the waiting time is up, the thread waiting on this object is released from the wait set and re-enters the dispatch queue (ready queue) )middle
- notify: select a thread in the wait set of the notified object to release;
- notifyAll: Release all threads on the wait set of the notified object.
Note:
The notified thread may not be able to resume execution immediately after being woken up, because the place where it was interrupted was in the synchronization block, and at this moment it no longer holds the lock, so she needs to try to acquire the lock again (probably facing other Thread competition), only after success can the execution be resumed at the place after the wait method was originally called.
Summarized as follows:
- If the lock can be acquired, the thread changes from the WAITING state to the RUNNABLE (runnable) state;
- Otherwise, the thread changes from the WAITING state to the BLOCKED (waiting for lock) state
The details that need to be paid attention to when calling the wait and notify methods
- The wait method and the notify method must be called by the same lock object. Because: the corresponding lock object can wake up the thread after the wait method called with the same lock object through notify.
- The wait method and the notify method belong to the methods of the Object class. Because: the lock object can be any object, and the class of any object inherits the Object class.
- The wait method and notify method must be used in a synchronization code block or a synchronization function, and these two methods must be called through the lock object.
Release lock operation and deadlock
1. The operation of releasing the lock
-
The execution of the synchronization method and synchronization code block of the current thread ends.
-
An unhandled Error or Exception occurs in the synchronization code block or synchronization method of the current thread, causing the current thread to end abnormally.
-
The current thread executes the wait() method of the lock object in the synchronization code block and synchronization method, the current thread is suspended, and the lock is released.
2. Deadlock
Different threads lock the synchronization monitor object required by the other party and do not release it. When they are waiting for the other party to give up first, a thread deadlock is formed. Once a deadlock occurs, the entire program will neither be abnormal nor give any prompts, but all threads are blocked and cannot continue.
3. Interview question: the difference between sleep() and wait() methods
(1) sleep() does not release the lock, wait() releases the lock
(2) sleep() specifies the sleep time, wait() can specify the time or wait indefinitely until notify or notifyAll
(3) sleep() is a static method declared in the Thread class, and the wait method is declared in the Object class
Because we call the wait() method is called by the lock object, and the type of the lock object is any type of object. Then the methods that you want any type of object to have can only be declared in the Object class.
practise
Two threads are required to print letters at the same time, and each thread can print 3 letters continuously. Two threads print alternately, one thread prints the lowercase form of the letter, and one thread prints the uppercase form of the letter, but the letters are consecutive. After the letter loops to z, go back to a.
public class PrintLetterDemo {
public static void main(String[] args) {
// 2、创建资源对象
PrintLetter p = new PrintLetter();
// 3、创建两个线程打印
new Thread("小写字母") {
public void run() {
while (true) {
p.printLower();
try {
Thread.sleep(1000);// 控制节奏
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread("大写字母") {
public void run() {
while (true) {
p.printUpper();
try {
Thread.sleep(1000);// 控制节奏
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
// 1、定义资源类
class PrintLetter {
private char letter = 'a';
public synchronized void printLower() {
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + "->" + letter);
letter++;
if (letter > 'z') {
letter = 'a';
}
}
this.notify();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void printUpper() {
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + "->" + (char) (letter - 32));
letter++;
if (letter > 'z') {
letter = 'a';
}
}
this.notify();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}