The latest Java basic series of courses--Day14-multithreaded programming

Author Homepage: Programming Compass

About the author: High-quality creator in the Java field, CSDN blog expert
, CSDN content partner, invited author of Nuggets, Alibaba Cloud blog expert, 51CTO invited author, many years of architect design experience, resident lecturer of Tencent classroom

Main content: Java project, Python project, front-end project, artificial intelligence and big data, resume template, learning materials, interview question bank, technical mutual assistance

Favorites, likes, don't get lost, it's good to follow the author

Get the source code at the end of the article

​#day09-multithreading

1. Multithreading

1.1 Basic concepts

A thread is actually a path of execution in a program.

Multithreading (Multithread) refers to the situation in which several executive bodies exist simultaneously in the same program and work together according to several different execution paths.

First, let's distinguish a few concepts:

程序(Program):程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,程序是静态的代码。

进程(Process):进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。

多任务(Multi task):多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每一个任务对应一个进程。

线程(Thread):线程是一个比进程更小的执行单位。一个进程在其执行过程中可以产生多个线程,形成多条执行线路。

The programs we wrote before are actually single-threaded programs, as shown in the code below, if the previous for loop is not executed, the code below the for loop will not be executed.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-tKkaiprB-1690973928863)(assets/1668046984412.png)]

What kind of program is a multi-threaded program? As shown in the figure below, the 12306 website supports multi-threading, because many people can enter the website to buy tickets at the same time, and each person does not affect each other. Another example is Baidu Netdisk, which can download or upload multiple files at the same time. In fact, there are multiple execution paths in these programs, and each execution execution path is a thread, so such a program is a multi-threaded program.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-yVA8EHps-1690973928865)(assets/1668047091631.png)]

1.2 Thread status and life cycle

Next, we learn the last knowledge point about threads, which is called the life cycle of threads. The so-called life cycle is what states the thread has in the process from birth to death, and how to switch between these states.

In order for everyone to understand the life cycle of threads, let's use the life cycle of a person as an example. There are several processes for a person from birth to death. During the life cycle of a person, there may be switches between various states, and threads are the same.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-cx8RhuCG-1690973928867)(assets/1668069740969.png)]

Next, let's learn the life cycle of threads. In the Thread class, there is a nested enumeration class called Thread.Status, which defines the 6 states of the thread. As shown below

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-RQ4TZPcP-1690973928868)(assets/1668069923403.png)]

NEW: 新建状态,线程还没有启动
RUNNABLE: 可以运行状态,线程调用了start()方法后处于这个状态
BLOCKED: 锁阻塞状态,没有获取到锁处于这个状态
WAITING: 无限等待状态,线程执行时被调用了wait方法处于这个状态
TIMED_WAITING: 计时等待状态,线程执行时被调用了sleep(毫秒)或者wait(毫秒)方法处于这个状态
TERMINATED: 终止状态, 线程执行完毕或者遇到异常时,处于这个状态。

The switching relationship between these states is shown in the figure below

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-VVZCdncj-1690973928871)(assets/1668070204768.png)]

1.3 Thread scheduling and priority

Scheduling: refers to the allocation of CPU resources between threads. There are two models of thread scheduling: time-sharing model and preemption model.
Priority: Determines the priority order in which threads are executed by the CPU.
The priority of threads in the Java language is represented by integers 1 to 10 from low to high, and is divided into 10 levels. The Thread class has three static variables about thread priority. MIN_PRIORITY represents the minimum priority, which is usually 1; MAX_PRIORITY represents the highest priority, which is usually 10;

Corresponding to a newly created thread, the system will assign priority to it according to the following principles:
(1) The newly created thread will inherit the priority of the parent thread that created it. The parent thread refers to the thread where the statement of creating a new thread object is executed. It may be the main thread of the program or a user-defined thread.
(2) In general, the main thread has normal priority.

1.4 Thread creation method 1

There are two ways to implement multi-threading in the Java language, one is to inherit the Thread class in the java.lang package, and the other is to implement the Runnable interface in the class defined by the user. But no matter which method is adopted, the Thread class and related methods in the Java language class library must be used.

Java provides developers with a class called Thread, and objects of this class are used to represent threads. The steps to create a thread and execute the thread are as follows

1.定义一个子类继承Thread类,并重写run方法
2.创建Thread的子类对象
3.调用start方法启动线程(启动线程后,会自动执行run方法中的代码)

code show as below

public class MyThread extends Thread{
    
    
    // 2、必须重写Thread类的run方法
    @Override
    public void run() {
    
    
        // 描述线程的执行任务。
        for (int i = 1; i <= 5; i++) {
    
    
            System.out.println("子线程MyThread输出:" + i);
        }
    }
}

Define another test class, create a MyThread thread object in the test class, and start the thread

public class ThreadTest1 {
    
    
    // main方法是由一条默认的主线程负责执行。
    public static void main(String[] args) {
    
    
        // 3、创建MyThread线程类的对象代表一个线程
        Thread t = new MyThread();
        // 4、启动线程(自动执行run方法的)
        t.start(); 

        for (int i = 1; i <= 5; i++) {
    
    
            System.out.println("主线程main输出:" + i);
        }
    }
}

The print result is shown in the figure below, we will find that MyThread and the main thread are robbing each other of the execution right of the CPU ( note: which thread executes first and which thread executes later, currently we cannot control, and the output results will be different each time )

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-rdJ9Vjn0-1690973928873)(assets/1668047848218.png)]

Finally, we need to pay attention to one more thing : you can't call the run method directly. If you call the run method directly, it is not considered a thread to start, but treat Thread as an ordinary object. At this time, the code executed in the run method will become the main thread. a part of. At this time, the execution result is like this.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-w8uukNX7-1690973928875)(assets/1668048108548.png)]

1.5 Thread creation method 2

Next we learn the second way to create threads. Java provides a Runnable interface for developers. There is only one run method in this interface, which means that the implementation class object of the Runnable interface is used to specifically represent the tasks to be performed by the thread. Specific steps are as follows

1.先写一个Runnable接口的实现类,重写run方法(这里面就是线程要执行的代码)
2.再创建一个Runnable实现类的对象
3.创建一个Thread对象,把Runnable实现类的对象传递给Thread
4.调用Thread对象的start()方法启动线程(启动后会自动执行Runnable里面的run方法)

The code is as follows: first prepare an implementation class of the Runnable interface

/**
 * 1、定义一个任务类,实现Runnable接口
 */
public class MyRunnable implements Runnable{
    
    
    // 2、重写runnable的run方法
    @Override
    public void run() {
    
    
        // 线程要执行的任务。
        for (int i = 1; i <= 5; i++) {
    
    
            System.out.println("子线程输出 ===》" + i);
        }
    }
}

Write another test class, create a thread object in the test class, and execute the thread

public class ThreadTest2 {
    
    
    public static void main(String[] args) {
    
    
        // 3、创建任务对象。
        Runnable target = new MyRunnable();
        // 4、把任务对象交给一个线程对象处理。
        //  public Thread(Runnable target)
        new Thread(target).start();

        for (int i = 1; i <= 5; i++) {
    
    
            System.out.println("主线程main输出 ===》" + i);
        }
    }
}

Run the above code, the result is as shown in the figure below** (Note: It is normal that the following alternate execution effects do not appear)**

主线程main输出 ===1
主线程main输出 ===2
主线程main输出 ===3
子线程输出 ===1
子线程输出 ===2
子线程输出 ===3
子线程输出 ===4
子线程输出 ===5
主线程main输出 ===4
主线程main输出 ===5

1.6 Thread creation method 2—anonymous inner class

Students pay attention, this way of writing is not new knowledge now. Just rewrite the second method above with an anonymous inner class. Because students may see this way of writing when they look at the code written by others. You know what's going on.

The second thread creation method we just learned requires writing an implementation class of the Runnable interface, and then passing the object of the Runnable implementation class to the Thread object.

Now I don't want to write the Runnable implementation class, so I can directly create an anonymous inner class object of the Runnable interface and pass it to the Thread object.

code show as below

public class ThreadTest2_2 {
    
    
    public static void main(String[] args) {
    
    
        // 1、直接创建Runnable接口的匿名内部类形式(任务对象)
        Runnable target = new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 1; i <= 5; i++) {
    
    
                    System.out.println("子线程1输出:" + i);
                }
            }
        };
        new Thread(target).start();

        // 简化形式1:
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 1; i <= 5; i++) {
    
    
                    System.out.println("子线程2输出:" + i);
                }
            }
        }).start();

        // 简化形式2:
        new Thread(() -> {
    
    
                for (int i = 1; i <= 5; i++) {
    
    
                    System.out.println("子线程3输出:" + i);
                }
        }).start();

        for (int i = 1; i <= 5; i++) {
    
    
            System.out.println("主线程main输出:" + i);
        }
    }
}

1.7 Thread creation method 3

Next, we learn the third way to create threads. There are already two, why should there be a third? In this way, we first analyze a problem that exists in the previous two. Then lead to the third can solve this problem.

  • Assuming that some data needs to be returned after the thread is executed, the run method rewritten in the previous two ways does not return a result.

    public void run(){
          
          
        ...线程执行的代码...
    }
    
  • JDK5 provides the Callable interface and the FutureTask class to create threads, and its biggest advantage is that it has a return value.

    There is a call method in the Callable interface, rewriting the call method is the code to be executed by the thread, and it has a return value

    public T call(){
          
          
        ...线程执行的代码...
        return 结果;
    }
    

The third way to create a thread, the steps are as follows

1.先定义一个Callable接口的实现类,重写call方法
2.创建Callable实现类的对象
3.创建FutureTask类的对象,将Callable对象传递给FutureTask
4.创建Thread对象,将Future对象传递给Thread
5.调用Threadstart()方法启动线程(启动后会自动执行call方法)call()方法执行完之后,会自动将返回值结果封装到FutrueTask对象中
   
6.调用FutrueTask对的get()方法获取返回结果

The code is as follows: first prepare an implementation class of the Callable interface

/**
 * 1、让子类继承Thread线程类。
 */
public class MyThread extends Thread{
    
    
    // 2、必须重写Thread类的run方法
    @Override
    public void run() {
    
    
        // 描述线程的执行任务。
        for (int i = 1; i <= 5; i++) {
    
    
            System.out.println("子线程MyThread输出:" + i);
        }
    }
}

Define another test class, create a thread in the test class and start the thread, and get the return result

public class ThreadTest3 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 3、创建一个Callable的对象
        Callable<String> call = new MyCallable(100);
        // 4、把Callable的对象封装成一个FutureTask对象(任务对象)
        // 未来任务对象的作用?
        // 1、是一个任务对象,实现了Runnable对象.
        // 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。
        FutureTask<String> f1  = new FutureTask<>(call);
        // 5、把任务对象交给一个Thread对象
        new Thread(f1).start();


        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2  = new FutureTask<>(call2);
        new Thread(f2).start();


        // 6、获取线程执行完毕后返回的结果。
        // 注意:如果执行到这儿,假如上面的线程还没有执行完毕
        // 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。
        String rs = f1.get();
        System.out.println(rs);

        String rs2 = f2.get();
        System.out.println(rs2);
    }
}

2. Common methods of multithreading

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-srmSisG9-1690973928878)(assets/1668051403591.png)]

Let's demonstrate getName()the effect setName(String name)of using these currentThread()methods sleep(long time).

public class MyThread extends Thread{
    
    
    public MyThread(String name){
    
    
        super(name); //1.执行父类Thread(String name)构造器,为当前线程设置名字了
    }
    @Override
    public void run() {
    
    
        //2.currentThread() 哪个线程执行它,它就会得到哪个线程对象。
        Thread t = Thread.currentThread();
        for (int i = 1; i <= 3; i++) {
    
    
            //3.getName() 获取线程名称
            System.out.println(t.getName() + "输出:" + i);
        }
    }
}

In the test class, create a thread object and start the thread

public class ThreadTest1 {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new MyThread();
        t1.setName(String name) //设置线程名称;
        t1.start();
        System.out.println(t1.getName());  //Thread-0

        Thread t2 = new MyThread("2号线程");
        // t2.setName("2号线程");
        t2.start();
        System.out.println(t2.getName()); // Thread-1

        // 主线程对象的名字
        // 哪个线程执行它,它就会得到哪个线程对象。
        Thread m = Thread.currentThread();
        m.setName("最牛的线程");
        System.out.println(m.getName()); // main

        for (int i = 1; i <= 5; i++) {
    
    
            System.out.println(m.getName() + "线程输出:" + i);
        }
    }
}

Execute the above code, the effect is shown in the figure below, we found that each thread has its own name.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-5W2kFTUk-1690973928879)(assets/1668052028054.png)]

Finally, demonstrate the effect of the join method.

public class ThreadTest2 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // join方法作用:让当前调用这个方法的线程先执行完。
        Thread t1 = new MyThread("1号线程");
        t1.start();
        t1.join();

        Thread t2 = new MyThread("2号线程");
        t2.start();
        t2.join();

        Thread t3 = new MyThread("3号线程");
        t3.start();
        t3.join();
    }
}

The execution effect is that thread No. 1 is executed first, and then thread No. 2 is executed; thread No. 2 is executed, and then thread No. 3 is executed; thread No. 3 is executed after execution.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-Kx2KdfOz-1690973928882)(assets/1668052307537.png)]

Let's try again, remove the join() method, and see the execution effect. At this time, you will find that thread 2 has not finished executing thread 1 before executing ** (the effect only appears after multiple runs, depending on the personal computer, it may be normal for some students to not appear for a long time) **

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-c94dDYU8-1690973928883)(assets/1668052414444.png)]

3. Thread safety issues

Friends, we have learned how to create threads and the common methods of threads. Next, we will learn one of the most important issues when using threads in the actual development process, called thread safety.

3.1 Overview of Thread Safety Issues

  • First of all, what is thread safety issue?

Thread safety issues refer to business security issues that may arise when multiple threads operate on the same shared resource at the same time.

Let's demonstrate to the students through a case of withdrawing money. The case requirements are as follows

场景:小明和小红是一对夫妻,他们有一个共享账户,余额是10万元,小红和小明同时来取钱,并且2人各自都在取钱10万元,可能出现什么问题呢?

As shown in the figure below, Xiaoming and Xiaohong are assumed to be the same thread, and each thread of this class should complete the three-step operation before it can be considered as a completed withdrawal operation. But the actual execution process may be as follows

​ ① The little red thread only executes to judge whether the balance is sufficient (the condition is true), and then the execution right of the CPU is taken away by the little red thread.

​ ② Xiaohong thread also executes to judge whether the balance is sufficient (the condition is also true), and then the CPU execution right is taken away by Xiaoming thread.

​ ③ Xiaoming thread has already judged whether the balance is sufficient, so it directly executes step 2 and spits out 100,000 yuan. At this time, the shared account month is 0. Then the CPU execution right was taken away by the little red thread.

④ Since the little red thread has just judged whether the balance is sufficient, it directly executes step 2 and spits out 100,000 yuan. At this time, the shared account monthly is -100,000.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-Tx7YJelm-1690973928897)(assets/1668059112092.png)]

You will find that in this money withdrawal case, two people withdrew 100,000 yuan from the shared account, but the problem is that it is only 100,000 yuan! ! !

The problem in the above case of withdrawing money is a manifestation of the thread safety problem.

3.2 Code Demonstration of Thread Safety Issues

First define a shared account class

public class Account {
    
    
    private String cardId; // 卡号
    private double money; // 余额。

    public Account() {
    
    
    }

    public Account(String cardId, double money) {
    
    
        this.cardId = cardId;
        this.money = money;
    }

    // 小明 小红同时过来的
    public void drawMoney(double money) {
    
    
        // 先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        // 1、判断余额是否足够
        if(this.money >= money){
    
    
            System.out.println(name + "来取钱" + money + "成功!");
            this.money -= money;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        }else {
    
    
            System.out.println(name + "来取钱:余额不足~");
        }
    }

    public String getCardId() {
    
    
        return cardId;
    }

    public void setCardId(String cardId) {
    
    
        this.cardId = cardId;
    }

    public double getMoney() {
    
    
        return money;
    }

    public void setMoney(double money) {
    
    
        this.money = money;
    }
}

When defining a thread class that withdraws money

public class DrawThread extends Thread{
    
    
    private Account acc;
    public DrawThread(Account acc, String name){
    
    
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
    
    
        // 取钱(小明,小红)
        acc.drawMoney(100000);
    }
}

Finally, write another test class and create two thread objects in the test class

public class ThreadTest {
    
    
    public static void main(String[] args) {
    
    
         // 1、创建一个账户对象,代表两个人的共享账户。
        Account acc = new Account("ICBC-110", 100000);
        // 2、创建两个线程,分别代表小明 小红,再去同一个账户对象中取钱10万。
        new DrawThread(acc, "小明").start(); // 小明
        new DrawThread(acc, "小红").start(); // 小红
    }
}

Run the program, the execution effect is as follows. You will find that both of them have withdrawn 100,000 yuan, and the balance is -10.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-T0y87sMO-1690973928899)(assets/1668059997020.png)]

3.3 Thread Synchronization Scheme

In order to solve the previous thread safety problem, we can use the idea of ​​thread synchronization. The most common solution for synchronization is locking, which means that only one thread is allowed to lock each time, and access can only be accessed after locking, and the lock is automatically released after the access is completed, and then other threads can lock again.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-4vc8wpCe-1690973928900)(assets/1668060312733.png)]

After the little red thread finishes executing, change the balance to 0, and the lock will be released when it goes out. At this time, the Xiaoming thread can be locked and executed, as shown in the figure below.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-6WQKMfFD-1690973928901)(assets/1668060382390.png)]

Using the locking scheme can solve the problem that the previous two threads both take 100,000 yuan. How to lock it? Java provides three options

1.同步代码块
2.同步方法
3.Lock锁

2.4 Synchronized code blocks

Let's learn about synchronous code blocks first. Its role is to lock the code that accesses shared data to ensure thread safety.

//锁对象:必须是一个唯一的对象(同一个地址)
synchronized(锁对象){
    
    
    //...访问共享数据的代码...
}

Use synchronized code blocks to solve the thread safety problem in the previous code. We only need to modify the code in the DrawThread class.

// 小明 小红线程同时过来的
public void drawMoney(double money) {
    
    
    // 先搞清楚是谁来取钱?
    String name = Thread.currentThread().getName();
    // 1、判断余额是否足够
    // this正好代表共享资源!
    synchronized (this) {
    
    
        if(this.money >= money){
    
    
            System.out.println(name + "来取钱" + money + "成功!");
            this.money -= money;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        }else {
    
    
            System.out.println(name + "来取钱:余额不足~");
        }
    }
}

At this time, run the test class again to observe whether there will be unreasonable situations.

Finally, tell the students how to choose the lock object

1.建议把共享资源作为锁对象, 不要将随便无关的对象当做锁对象
2.对于实例方法,建议使用this作为锁对象
3.对于静态方法,建议把类的字节码(类名.class)当做锁对象

2.5 Synchronization method

Next, learn about synchronized methods to address thread safety issues. In fact, the synchronization method is to lock the entire method. When one thread calls this method, it cannot be executed when another thread calls it. Only after the previous thread call ends, the next thread call can continue to execute.

// 同步方法
public synchronized void drawMoney(double money) {
    
    
    // 先搞清楚是谁来取钱?
    String name = Thread.currentThread().getName();
    // 1、判断余额是否足够
    if(this.money >= money){
    
    
        System.out.println(name + "来取钱" + money + "成功!");
        this.money -= money;
        System.out.println(name + "来取钱后,余额剩余:" + this.money);
    }else {
    
    
        System.out.println(name + "来取钱:余额不足~");
    }
}

After the modification, run the test class again to see if there is any unreasonable situation.

Next, ask the students another question, does the synchronization method have a lock object? Who is locked?

同步方法也是有锁对象,只不过这个锁对象没有显示的写出来而已。
	1.对于实例方法,锁对象其实是this(也就是方法的调用者)
	2.对于静态方法,锁对象时类的字节码对象(类名.class

Finally, to summarize what is the difference between a synchronized code block and a synchronized method?

1.不存在哪个好与不好,只是一个锁住的范围大,一个范围小
2.同步方法是将方法中所有的代码锁住
3.同步代码块是将方法中的部分代码锁住

2.6 Lock lock

Next, let's learn another solution to the thread safety problem, called Lock lock.

Lock lock is a kind of lock object specially provided by JDK5 version. Through the method of this lock object to achieve the purpose of locking and releasing lock, it is more flexible to use. The format is as follows

1.首先在成员变量位子,需要创建一个Lock接口的实现类对象(这个对象就是锁对象)
	private final Lock lk = new ReentrantLock();
2.在需要上锁的地方加入下面的代码
	 lk.lock(); // 加锁
	 //...中间是被锁住的代码...
	 lk.unlock(); // 解锁

Use the Lock lock to rewrite the method of withdrawing money in the previous DrawThread, the code is as follows

// 创建了一个锁对象
private final Lock lk = new ReentrantLock();

public void drawMoney(double money) {
    
    
        // 先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        try {
    
    
            lk.lock(); // 加锁
            // 1、判断余额是否足够
            if(this.money >= money){
    
    
                System.out.println(name + "来取钱" + money + "成功!");
                this.money -= money;
                System.out.println(name + "来取钱后,余额剩余:" + this.money);
            }else {
    
    
                System.out.println(name + "来取钱:余额不足~");
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock(); // 解锁
        }
    }
}

Run the program results to see if there are thread safety issues. So far, we have finished learning three ways to solve thread safety problems.

Fourth, thread communication (understand)

Next, let's learn about thread communication.

First of all, what is thread communication?

  • When multiple threads jointly operate shared resources, the threads inform each other of their status in a certain way to coordinate with each other and avoid invalid resource grabbing.

Common mode of thread communication: producer and consumer model

  • The producer thread is responsible for generating data
  • The consumer thread is responsible for consuming the data generated by the producer
  • Note: The producer should let himself wait after producing the data and notify other consumers to consume; after the consumer consumes the data, he should let himself wait and notify the producer to generate.

For example, in the following case, there are 3 chefs (producer threads) and two customers (consumer threads).

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-Vi5gk2hh-1690973928902)(assets/1668064583299.png)]

Next, let's analyze the idea of ​​completing this case first.

1.先确定在这个案例中,什么是共享数据?
	答:这里案例中桌子是共享数据,因为厨师和顾客都需要对桌子上的包子进行操作。

2.再确定有那几条线程?哪个是生产者,哪个是消费者?
	答:厨师是生产者线程,3条生产者线程; 
	   顾客是消费者线程,2条消费者线程
	   
3.什么时候将哪一个线程设置为什么状态
	生产者线程(厨师)放包子:
		 1)先判断是否有包子
		 2)没有包子时,厨师开始做包子, 做完之后把别人唤醒,然后让自己等待
		 3)有包子时,不做包子了,直接唤醒别人、然后让自己等待
		 	
	消费者线程(顾客)吃包子:
		 1)先判断是否有包子
		 2)有包子时,顾客开始吃包子, 吃完之后把别人唤醒,然后让自己等待
		 3)没有包子时,不吃包子了,直接唤醒别人、然后让自己等待

Write the code according to the above analysis. First write the table class, the code is as follows

public class Desk {
    
    
    private List<String> list = new ArrayList<>();

    // 放1个包子的方法
    // 厨师1 厨师2 厨师3
    public synchronized void put() {
    
    
        try {
    
    
            String name = Thread.currentThread().getName();
            // 判断是否有包子。
            if(list.size() == 0){
    
    
                list.add(name + "做的肉包子");
                System.out.println(name + "做了一个肉包子~~");
                Thread.sleep(2000);

                // 唤醒别人, 等待自己
                this.notifyAll();
                this.wait();
            }else {
    
    
                // 有包子了,不做了。
                // 唤醒别人, 等待自己
                this.notifyAll();
                this.wait();
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    // 吃货1 吃货2
    public synchronized void get() {
    
    
        try {
    
    
            String name = Thread.currentThread().getName();
            if(list.size() == 1){
    
    
                // 有包子,吃了
                System.out.println(name  + "吃了:" + list.get(0));
                list.clear();
                Thread.sleep(1000);
                this.notifyAll();
                this.wait();
            }else {
    
    
                // 没有包子
                this.notifyAll();
                this.wait();
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Then write the test class, in the test class, create 3 chef thread objects, create 2 customer objects, and start all threads

public class ThreadTest {
    
    
    public static void main(String[] args) {
    
    
        //   需求:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上
        //      2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。
        Desk desk  = new Desk();

        // 创建3个生产者线程(3个厨师)
        new Thread(() -> {
    
    
            while (true) {
    
    
                desk.put();
            }
        }, "厨师1").start();

        new Thread(() -> {
    
    
            while (true) {
    
    
                desk.put();
            }
        }, "厨师2").start();

        new Thread(() -> {
    
    
            while (true) {
    
    
                desk.put();
            }
        }, "厨师3").start();

        // 创建2个消费者线程(2个吃货)
        new Thread(() -> {
    
    
            while (true) {
    
    
                desk.get();
            }
        }, "吃货1").start();

        new Thread(() -> {
    
    
            while (true) {
    
    
                desk.get();
            }
        }, "吃货2").start();
    }
}

Execute the above code, and the running results are as follows: You will find that multiple threads coordinate execution with each other to avoid invalid resource grabbing.

厨师1做了一个肉包子~~
吃货2吃了:厨师1做的肉包子
厨师3做了一个肉包子~~
吃货2吃了:厨师3做的肉包子
厨师1做了一个肉包子~~
吃货1吃了:厨师1做的肉包子
厨师2做了一个肉包子~~
吃货2吃了:厨师2做的肉包子
厨师3做了一个肉包子~~
吃货1吃了:厨师3做的肉包子

Five, thread pool

5.1 Overview of thread pool

Friends, let's learn about thread pool technology next. Let's first understand what is thread pool technology? In fact, the thread pool is a technology that can reuse threads .

To understand what thread reuse technology is, we must first look at what problems will arise if we do not use a thread pool. After understanding these problems, it will be easy for us to explain thread reuse to students.

假设:用户每次发起一个请求给后台,后台就创建一个新的线程来处理,下次新的任务过来肯定也会创建新的线程,如果用户量非常大,创建的线程也讲越来越多。然而,创建线程是开销很大的,并且请求过多时,会严重影响系统性能。

Using the thread pool can solve the above problems. As shown in the figure below, there will be a container inside the thread pool to store several core threads. Suppose there are 3 core threads, and these 3 core threads can process 3 tasks.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-zOCVdcFj-1690973928903)(assets/1668065892511.png)]

But when the task is always executed, assuming that the task of the first thread is executed, then the first thread will be idle, and when there is a new task, the idle first thread can perform other tasks. Based on this, these three threads can be continuously reused, and can also perform many tasks.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-7Y5Mwr4f-1690973928904)(assets/1668066073126.png)]

Therefore, the thread pool is a thread multiplexing technology, which can improve the utilization rate of threads.

5.2 Create a thread pool

In the JDK5 version, the interface ExecutorService representing the thread pool is provided, and there is an implementation class under this interface called the ThreadPoolExecutor class. The ThreadPoolExecutor class can be used to create a thread pool object.

The following is its constructor, there are many parameters, don't be afraid, just do it_ .

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-k5SfxrMB-1690973928906)(assets/1668066279649.png)]

Next, use the constructor of these 7 parameters to create the object of the thread pool. code show as below

ExecutorService pool = new ThreadPoolExecutor(
    3,	//核心线程数有3个
    5,  //最大线程数有5个。   临时线程数=最大线程数-核心线程数=5-3=2
    8,	//临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
    TimeUnit.SECONDS,//时间单位(秒)
    new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待
    Executors.defaultThreadFactory(), //用于创建线程的工厂对象
    new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);

Regarding the thread pool, we need to pay attention to the following two issues

  • When are temporary threads created?

    新任务提交时,发现核心线程都在忙、任务队列满了、并且还可以创建临时线程,此时会创建临时线程。
    
  • When do you start rejecting new tasks?

    核心线程和临时线程都在忙、任务队列也满了、新任务过来时才会开始拒绝任务。
    

5.3 Thread pool executes Runnable tasks

After creating the thread pool, we can then use the thread pool to perform tasks. There are two types of tasks executed by the thread pool, one is the Runnable task; the other is the callable task. The execute method below can be used to execute Runnable tasks.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-v50JguNC-1690973928907)(assets/1668066844202.png)]

First prepare a thread task class

public class MyRunnable implements Runnable{
    
    
    @Override
    public void run() {
    
    
        // 任务是干啥的?
        System.out.println(Thread.currentThread().getName() + " ==> 输出666~~");
        //为了模拟线程一直在执行,这里睡久一点
        try {
    
    
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

The following is the code to execute the Runnable task, pay attention to read the comments, and understand it against the previous 7 parameters.

ExecutorService pool = new ThreadPoolExecutor(
    3,	//核心线程数有3个
    5,  //最大线程数有5个。   临时线程数=最大线程数-核心线程数=5-3=2
    8,	//临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
    TimeUnit.SECONDS,//时间单位(秒)
    new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待
    Executors.defaultThreadFactory(), //用于创建线程的工厂对象
    new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);

Runnable target = new MyRunnable();
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
//下面4个任务在任务队列里排队
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);

//下面2个任务,会被临时线程的创建时机了
pool.execute(target);
pool.execute(target);
// 到了新任务的拒绝时机了!
pool.execute(target);

Execute the above code, the output is as follows

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-mH1OaQ3x-1690973928908)(assets/1668067745116.png)]

5.4 Thread pool executes Callable tasks

Next, we learn to use the thread pool to execute Callable tasks. Compared with the Runnable task, the callable task has one more return value.

The following submit method is required to execute the Callable task

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-avnZKVvg-1690973928909)(assets/1668067798673.png)]

First prepare a Callable thread task

public class MyCallable implements Callable<String> {
    
    
    private int n;
    public MyCallable(int n) {
    
    
        this.n = n;
    }

    // 2、重写call方法
    @Override
    public String call() throws Exception {
    
    
        // 描述线程的任务,返回线程执行返回后的结果。
        // 需求:求1-n的和返回。
        int sum = 0;
        for (int i = 1; i <= n; i++) {
    
    
            sum += i;
        }
        return Thread.currentThread().getName() + "求出了1-" + n + "的和是:" + sum;
    }
}

Prepare another test class, create a thread pool in the test class, and execute callable tasks.

public class ThreadPoolTest2 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 1、通过ThreadPoolExecutor创建一个线程池对象。
        ExecutorService pool = new ThreadPoolExecutor(
            3,
            5,
            8,
            TimeUnit.SECONDS, 
            new ArrayBlockingQueue<>(4),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy());

        // 2、使用线程处理Callable任务。
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));

        // 3、执行完Callable任务后,需要获取返回结果。
        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

After execution, the result is shown in the figure below

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-BwdNc8Hr-1690973928910)(assets/1668067964048.png)]

5.5 Thread pool tools (Executors)

Some students may think that there are too many code parameters for creating a thread pool, and they cannot remember. Is there a quick way to create a thread pool? some. Java provides developers with a tool class for creating thread pools, called Executors, which provides methods to create various thread pools that cannot be characterized. As shown below

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-ay19Xink-1690973928911)(assets/1668068110593.png)]

Next, we demonstrate the creation of a thread pool with a fixed number of threads. These methods are not used much, so I won’t do too many demonstrations here, just learn about them.

public class ThreadPoolTest3 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 1、通过Executors创建一个线程池对象。
        ExecutorService pool = Executors.newFixedThreadPool(17);
        // 老师:核心线程数量到底配置多少呢???
        // 计算密集型的任务:核心线程数量 = CPU的核数 + 1
        // IO密集型的任务:核心线程数量 = CPU核数 * 2

        // 2、使用线程处理Callable任务。
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

Executors create a thread pool so easy to use, why not recommend students to use it? The reason is here: look at the figure below, which is the mandatory specification requirement provided by the "Alibaba Java Development Manual".

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-IZPR6r83-1690973928912)(assets/1668068399363.png)]

6. Supplementary knowledge

Finally, let's add a few more conceptual knowledge points. It is enough for students to know what these concepts mean.

6.1 Concurrency and Parallelism

First learn the first supplementary knowledge point, concurrency and parallelism. Before explaining the meaning of concurrency and parallelism, let's first understand what are processes and threads?

  • A program (software) that runs normally is an independent process
  • A thread belongs to a process, and a process contains multiple threads
  • The threads in the process actually exist concurrently and in parallel (continue to look down)

We can open the task manager of the system to see (shortcut key: Ctrl+Shfit+Esc), what processes are currently on our computer.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-aVwqGqrh-1690973928913)(assets/1668069176927.png)]

After knowing what processes and threads are, let's learn the meaning of concurrency and parallelism.

First of all, let's learn what is concurrency?

The threads in the process are scheduled and executed by the CPU, but the number of threads processed by the CPU at the same time is prioritized. In order to ensure that all threads can be executed, the CPU uses a polling mechanism to serve each thread in the system. Because the CPU switching speed is very fast , giving us the feeling that these threads are executing at the same time, which is concurrency. (Simply remember: concurrency is the alternate execution of multiple threads)

Next, let's learn what is parallelism?

Parallelism means that multiple threads are scheduled and executed by the CPU at the same time. As shown in the figure below, multiple CPU cores are executing multiple threads

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-9DXtLgU7-1690973928914)(assets/1668069524799.png)]

The last question, is multithreading concurrent or parallel?

In fact, multiple threads are executed on our computer, and concurrency and parallelism exist at the same time.

Guess you like

Origin blog.csdn.net/whirlwind526/article/details/132069371