Multithreading
Highly recommend teacher Liao Xuefeng’s website
The difference between threads and processes
In computers, we call a task a process. Multiple subtasks can be executed simultaneously within a process. We call subtasks threads.
That is: a process contains at least one thread.
Java multithreading
A Java program is a JVM process that uses a main thread to execute a main()
method, main()
and multiple threads can exist inside the method.
Create thread
Thread creation
public class Main{
public static void main(String[] args) {
Thread thread = new Thread();
t.start(); //启动线程t
}
}
Use threads to perform tasks
method one
Make a Thread
subclass of a class and override run()
methods.
public class Main{
public static void main(String[] args) {
Thread thread = new ThreadTest();
t.start();
}
}
class ThreadTest extends Thread {
@Override
public void run() {
System.out.println("Start!");
}
}
After the start method is executed, the run method is automatically called to output Start
.
Method Two
Through the Runnable
interface.
public class Main{
public static void main(String[] args) {
Thread thread = new ThreadTest(new Runnable());
t.start();
}
}
class ThreadTest implements Runnable{
@Override
public void run() {
System.out.println("Start!");
}
}
nature
-
As you can see,
run()
when calling the method, there is not.run()
way to use it.If you use this method, essentially just call a common Java method to print the statement to complete the task, and no new thread is created, and all operations are performed in the
main
thread.Thread
Thestart()
method must be called to start a new thread. Theprivate native void start0()
method is called internally .native
The modifier indicates that this method is implemented by the C language code inside the JVM virtual machine. -
The threads are executed concurrently. That is, the scheduling order between threads cannot be determined by the program itself.
such as:
public class Main { public static void main(String[] args) { System.out.println("main run"); Thread t = new Thread() { public void run() { System.out.println("thread run"); System.out.println("thread end"); } }; t.start(); System.out.println("main end"); } }
In addition to determining the output first
main run
, the output order of the sentences cannot be determined. To simulate the effect of concurrent execution, you can call aThread.sleep()
method to force the current thread to temporarily set a period of time.
Thread priority
Thread.setPriority(int n); // 级别1-10, 默认5
Note that threads with high priority will only be scheduled more frequently by the operating system, and priority cannot be set to ensure that threads with high priority will be executed first.
If you want to make a thread run after another thread ends, you can use join()
methods.
class Main {
public static void main(String[] args) {
System.out.println("Main Run");
Thread t = new Thread() {
public void run () {
System.out.println("Thread Run");
System.out.println("Thread End");
}
};
try {
t.join();
// 可以指定时间,当指定时间后线程未结束则不再等待。
// 如果在指定时间内完成,线程仍会继续等待,指定到达指定时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main End");
}
}
Thread status
The Java thread has the following states:
- New: The newly created thread has not been executed yet;
- Runnable: the running thread is scheduled by the CPU at any time;
- Blocked: The running thread is suspended due to some operations being blocked;
- Waiting: running thread, waiting for some operation;
- Timed Waiting: The running thread
sleep()
waits for time due to the execution method; - Terminated: The thread has been terminated because the
run()
method has finished executing.
In a Java program, a thread object can only call a start()
method once .
After the start, thread Runnable
, Blocked
, Waiting
, Timed Waiting
between state switch until it becomes Terminated
a state.
Reason for thread termination
- Normal termination: the
run()
method has been executed; - Unexpected termination: the
run()
method encountered an uncaught exception; - Forced termination: call a
stop()
method on the thread instance (not recommended).
Thread interruption
method one
In the process of a thread performing a task, we may need to interrupt this thread and execute other threads, then we need to use interrupt()
methods.
class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new ThreadTest();
// t线程启动
t.start();
// 当前线程暂停, 执行t线程
Thread.sleep(1000);
// 暂停结束,二者并行, 打断t线程
t.interrupt();
// 等待t线程结束
t.join();
System.out.println("Main End");
}
}
class ThreadTest extends Thread {
@Override
public void run() {
// 新建p线程
Thread p = new ThreadTest2();
// p线程启动
p.start();
try {
// 等待p线程结束
p.join();
} catch (InterruptedException e) {
System.out.println("T was Interrupted!");
}
p.interrupt();
}
}
class ThreadTest2 extends Thread {
@Override
public void run() {
int n = 0;
// 在未被中止前持续循环
while (!isInterrupted()) {
System.out.println(n++ + "asdf");
try {
// 当前线程暂停
Thread.sleep(100);
} catch (InterruptedException e) {
// System.out.println("P was Interrupted!");
// 被中止后break跳出循环
break;
}
}
}
}
main
The thread calls the t.interrupt()
method to interrupt the t
thread. At this time, the t
thread is waiting for the p
thread to end. The t.interrupt()
method immediately ends the t
thread and throws InterruptedException
an exception. Before the t
thread ends, the p
thread is p.interrupt()
called to p
interrupt the thread.
If this operation is not performed, the p
thread will continue to run and the JVM will not exit. Sometimes, we have to make some threads loop indefinitely, so when we want to exit, who will close these threads?
So there is daemon thread
Method Two
We can determine whether the thread continues to run by setting the flag bit.
class Main {
public static void main(String[] args) throws InterruptedException {
// 注意这里是子类的引用
ThreadTest t = new ThreadTest();
// 启动线程t
t.start();
// 当前线程暂停
Thread.sleep(1000);
// 改变标志位的值
t.running = false;
}
}
class ThreadTest extends Thread {
// 标志位
public volatile boolean running = true;
@Override
public void run() {
int n = 0;
while (running) {
System.out.println(n++ + "asdf");
}
System.out.println("T End");
}
}
Note that a volatile
keyword pair is running
used for marking. The keyword is used to mark shared variables between threads to ensure that each thread can read the updated variable value.
Shared variables between threads
Why use this keyword?
This involves Java's memory model. In the JVM, the value of the variable is stored in the main memory. When a thread accesses a variable, it first obtains a copy and saves it in the working memory of the thread.
If the thread modifies the variable, the JVM willSometimeWrite the modified value to the main memory, thistimeIs uncertain! Therefore, after the variable is changed, the variable accessed by a thread is still the value of the variable before the change.
So we need to use keywords to volatile
tell JVM:
- Every time a variable is accessed, the latest value of the main memory is always obtained;
- Every time a variable is modified, it is written to the main memory immediately.
Daemon thread
When a user thread loops infinitely, we cannot terminate the JVM normally, but we really need this infinite looping thread, such as the Java garbage collection thread, how to do it? Daemon thread.
Daemon threads are used to serve user threads. In JVM, after all user threads are finished, the virtual machine will exit regardless of whether there are daemon threads running.
Creation of daemon threads
Thread t = new ThreadTest();
t.setDaemon(true);
t.start();
nature
-
You cannot set the started thread as a daemon thread, ie
t.start(); t.setDaemon(true); // 将会抛出异常
Is wrong,
IllegalThreadStateException
an exception will be thrown . -
The new thread generated in the daemon thread is also
Daemon
the same, that is, it is also a daemon thread; -
The daemon thread cannot access inherent resources such as files. Because it may be interrupted at any time;
-
Java's own multithreading framework, for example
ExecutorService
, converts daemon threads into user threads, so if you want to use background threads, you cannot use Java's thread pool.
Thread synchronization
When multiple threads are running at the same time, the scheduling of threads is determined by the operating system, so any thread may be suspended by the operating system at any instruction, and then continue to execute after a certain period of time. Then, if multiple threads read and write shared variables at the same time, there will be data inconsistencies.
Such as:
class Main {
public static void main(String[] args) throws Exception {
ThreadAdd add = new ThreadAdd();
ThreadDec dec = new ThreadDec();
add.start();
dec.start();
add.join();
dec.join();
System.out.println(Counter.count);
}
}
class Counter {
public static int cnt = 0;
}
class ThreadAdd extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
Counter.cnt++;
}
}
}
class ThreadDec extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
Counter.cnt--;
}
}
}
What is the result of this procedure? 0?
In fact, the result of each run is different.
Atomic manipulation
When reading and writing variables, you must ensure that the operation is an atomic operation, that is, an operation that cannot be interrupted.
The JVM specifies the following operations as atomic operations:
- Basic type assignment;
- Reference type assignment.
Lock and unlock
If the operation is a non-atomic operation, the operation may be interrupted and result in incorrect results during the simultaneous execution of several threads.
In order to avoid this situation, we need to use lock and unlock operations.
Locking and unlocking operations can ensure that several instructions are always executed in one thread, and no other threads will enter this instruction range. Even if the thread is interrupted during execution, other threads will not be able to enter this instruction range because they cannot obtain the lock. Only after the execution thread releases the lock, that is, after unlocking, other threads have the opportunity to obtain the lock and execute.
This kind of code block between locking and unlocking is called 临界区
, at most one thread executes in the critical section at any time.
Locking and unlocking can be seen as implementing non-atomic operations into atomic operations. Easy to get, atomic operations do not require locking.
Implementation of lock and unlock
We synchronized
lock an object through keywords.
which is:
- Find out the thread code block that modifies the shared variable;
- Choose a shared instance as the lock;
- Use
synchronized(lockObject) {...}
.
An example is this:
synchronized(lock) {
//加锁
n += 1;
} //自动解锁
After the above code is locked, it is rewritten as:
class Main {
public static void main(String[] args) throws Exception {
ThreadAdd add = new ThreadAdd();
ThreadDec dec = new ThreadDec();
add.start();
dec.start();
add.join();
dec.join();
System.out.println(Counter.count);
}
}
class Counter {
public static final Object lock = new Object();
public static int cnt = 0;
}
class ThreadAdd extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
synchronized(Counter.lock) {
Counter.cnt++;
}
}
}
}
class ThreadDec extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
synchronized(Counter.lock) {
Counter.cnt--;
}
}
}
}
The code:
synchronized(Counter.lock) {
//....
}
Among them, the Counter.lock
instance is used as the lock, and the two threads obtain the lock in the critical section before performing the operation.
After the execution ends and the statement block ends, the lock is automatically released.
Reference
- https://www.liaoxuefeng.com/wiki/1252599548343744/1255943750561472
- https://zhuanlan.zhihu.com/p/28049750