Summary of Java Multithreading Basics (Part 1)

Table of contents

1. Concept

 2. Thread creation

3. Common methods of the Thread class

1. Start a thread

2. Terminate a thread

3. Waiting for a thread

4. Thread safety issues

1. Causes of thread safety:

 2. How to solve the thread safety problem

2.1 synchronized keyword

2.2 volatile keyword

3. wait and notify

4. The difference between wait and sleep (interview questions)



1. Concept

     Java provides built-in support for multithreaded programming. A thread refers to a single sequential flow of control in a process. Multiple threads can run concurrently in a process, and each thread performs different tasks in parallel.

     Thread: A thread is an "execution flow". Each thread can execute its own code according to the sequence. Multiple codes are executed "simultaneously" among multiple threads. In layman's terms, different functions are executed in an application. For example, we use WeChat to chat, and WeChat runs like a process, while we use WeChat to chat, post to Moments, etc., which are equivalent to different threads.

     Process: It is an instance of a running program. It is a running activity of a program with certain independent functions on a certain data set. In layman's terms, it is equivalent to a running program.

     For example: Take eating as an example. While eating, we can play mobile phones, watch TV, chat, etc. A series of behaviors, we can regard eating as a process, and Behaviors such as playing mobile phones, watching TV, and chatting can be regarded as an independent thread. When these behaviors are carried out together, we can regard them as a multi-threaded task


 2. Thread creation

Repost my previous blog: Java Thread Creation_Bc_Xiao Xu's Blog-CSDN Blog


3. Common methods of the Thread class

1. Start a thread

If we want to start a thread, we call the start method of the Thread class to solve

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程1");
        });
        //启动线程
        thread.start();
        System.out.println("线程2");
    }

When we call the start method, this really creates a thread at the bottom of the operating system, and this thread enters the ready state, waiting for the scheduling of the CPU;

2. Terminate a thread

In multithreading, we usually terminate the running of a thread by setting a flag bit

class Mythread implements Runnable{
    //设置一个标志位,通过改变标志位的值,来终止线程的进行
    public boolean flag = true;
    @Override
    public void run() {
        while (flag){
            for (int i = 0; i < 999; i++) {
                System.out.println(i);
            }
        }
    }
}
public class Demo3 {
    public static void main(String[] args) {
      Mythread mythread = new Mythread();
      Thread thread = new Thread(mythread);
        thread.start();
        for (int i = 0; i < 999; i++) {
            System.out.println(i);
            if(i == 666){
              //改变标志位的值
                mythread.flag = false;
                System.out.println("该线程该停止了");
            }
        }
    }
}

3. Waiting for a thread

In the thread, sometimes we need to wait for a thread to finish executing first, and then proceed to the next thread. At this time, the Thread standard library provides a join method, let us implement:

public class Demo4 {
    //等待一个线程
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("线程1");

        });
        //启动线程
        thread.start();
        //在main函数里调用join,意思是让main线程等待thread线程执行完,main线程才执行
        thread.join();
        System.out.println("主线程");
    }
}

 

From the execution results, we can see that the join method is called in the main function here, which means that the thread thread is executed first, and the main thread is executed before it is executed. If the thread thread has not been executed, the main thread will be in a state called In the state of blocking and waiting, it will not participate in the scheduling of the CPU. Until the thread thread is executed, the blocking will be released and the execution will continue;


4. Thread safety issues

1. Causes of thread safety:

Regarding the issue of thread safety, I think it has always been an important issue in Java. We must always ensure that there is nothing wrong with writing programs. Regarding thread safety, you can give an example:

class Counter {
    public int count = 0;

    public void add() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.add();
            }

        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.add();
            }

        });
        //启动线程
        thread1.start();
        thread2.start();

        //等待线程
        thread1.join();
        thread2.join();

        System.out.println(counter.getCount());
    }

In the above code, we use threads to perform ++ operations on count, and let thread1 and thread2 each loop 1000 times. If it is executed according to the normal logic, the last result we output is 2000, but the actual output result is not Not so, as you can see in the figure below, we printed 3 times respectively, and the results are different each time, so

 

 


This involves the issue of thread safety. The reason for this bug is actually mainly related to the randomness of thread scheduling. For this count++ operation, from the perspective of the CPU, it is equivalent to three instructions to complete:

The first step: Load operation: read the data in the memory into the CPU register

The second step: Add: perform +1 operation on the value of the register

Step 3: Save: Write the value of the register back to the memory

These three steps are also performed randomly in the two threads concurrently, which is equivalent to the random scheduling of threads, which is a large range. For the operation of ++, these three instructions are also executed randomly, which is large. The small range below the range, which leads to why the final result is not the same as we expected;

Each of the serial numbers above is a case of:

   Taking the second picture as an example, the load, add, and save of thread1 are executed first. When all three instructions of thread1 are executed, an operation of adding 1 is performed at this time. After thread1 is executed, three instructions of thread2 are executed again;

   Take the third picture as an example, the load instruction of thread1 is executed first, and then the loaf add save operation of thread2 is executed. At this time, the addition operation of thread1 has not been executed, so 1 will not be added. When the three operations of thread2 are executed Finally, execute the remaining two instructions (add, save) of thread1. After these two instructions are executed, it is a complete plus 1 operation;

This is also the main reason for thread safety: it is not executed sequentially, but randomly scheduled;


 2. How to solve the thread safety problem

2.1 synchronized keyword

    The above-mentioned thread insecurity problem is mainly caused by the random scheduling of three instructions. In order to solve this problem, we can lock the operation and encapsulate the operation of these three instructions, which means that they should be executed together and cannot be dispersed. Execution, this also ensures the atomicity of the operation, so that even if two threads execute concurrently, the operation to be performed is atomic, so-called atomic, is indivisible;

The operation of locking in Java is to use the synchronized keyword:

In the code block modified by synchronized, the lock will be triggered, and the synchronized code block will be unlocked;

public void add() {
        synchronized (this){
            count++;
        }
    }

 About the writing of synchronized:

If for normal method:

第一种写法,锁住的对象就是当前的操作
public void add() {
        synchronized (this){
            count++;
        }

    }
第二种写法,直接修饰这个方法

synchronized public void add(){
        count++;
    }

If for a static member method:

当修饰静态方法的时候
第一种写法,synchronized后面锁住的对象就是当前类对象
public static void add(){
        synchronized (Counter.class){

        }
    }
第二种写法,直接修饰这个静态方法    
synchronized public static void add(){
        
    }

2.2 volatile keyword

There are many reasons for thread safety, not just atomicity. For example, the following piece of code:

public class Demo6 {
    private static int ret = 0;

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (ret == 0) {

            }
            System.out.println("线程结束");
        });
        Thread thread2 = new Thread(() -> {
            Scanner scan = new Scanner(System.in);
            ret = scan.nextInt();
        });
        thread1.start();
        thread2.start();
    }
}

In the above code, we end the progress of the thread by inputting the value of ret. When we input a non-zero number, according to the logic of the code, we can end thread 1, and the printing thread is over, but the expected result is different from our actual output The result is not the same, when we run the program we can see:

The thread has never ended, and the end of the thread is not printed. The reason for this is the decision made by the compiler optimization.

 So here is equivalent to no matter how you enter, it is always compared with the value you get for the first time. In order to solve this problem, we need to use the volatile keyword to de-optimize the compiler to ensure that the value you get is the value you downloaded The value entered once, instead of comparing with the value obtained for the first time;

After adding the volatile keyword, the output result is the same as we expected, and the variable modified by volatile prohibits compiler optimization, ensuring that the data is re-read from the memory every time;

Note: But volatile cannot guarantee atomicity. The scenario it uses is one thread reads and one thread writes;


3. wait and notify

     Since the threads are preemptively executed, the order of execution between threads is difficult to predict. However, in actual development, sometimes we hope to reasonably coordinate the order of execution among multiple threads. At this time, the role of wait and notify is Here we come, we can use these two keywords to control the sequence of threads:

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread thread1 = new Thread(() -> {

            try {
                System.out.println("线程1中的wait开始");
                synchronized (locker) {
                    locker.wait();
                }
                System.out.println("线程1中的wait结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        //启动线程
        thread1.start();
        //线程休眠
        Thread.sleep(1000);

        Thread thread2 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("线程2中的notify开始");
                locker.notify();
                System.out.println("线程2中的notify结束");
            }
        });
        //启动线程
        thread2.start();
    }
}

In the above code, we use the two keywords wait and notify to let thread 1 start first, then use wait to let thread 1 enter the blocking state, and then execute thread 2. After thread 2 finishes executing, notify will play a role in waking up The role is to wake up thread 1 and let thread 1 finish executing, so that the order of thread execution can be flexibly controlled;


The main things wait does are:

1. Unlock (so before using wait, it must be locked, otherwise how to unlock)

2. Enter the blocking state

3. Wait to be woken up and get the lock again

The function of notify is to wake up the waiting method

4. The difference between wait and sleep (interview questions)

1.wait needs to be used with synchronized, but sleep does not

2.wait is the method of object, sleep is the method of Thread

Guess you like

Origin blog.csdn.net/m0_63635730/article/details/129783434