Multi-threaded learning - a must-see for getting started

Multi-threaded study notes

Simple learning of multi-threading, this section mainly focuses on learning multi-objectives:

  • The difference between threads and processes
  • How to create threads (two commonly used ones)
  • Thread status (5 types)
    • Thread methods (sleep, yield, join, wait, notify, notifyAll)
    • Thread priority
  • Thread synchronization
    • Concurrency (not thread-safe)
    • Synchronized & lock
    • deadlock
  • Thread collaboration (communication)
    • Pipeline method
    • signal light method
  • Thread Pool

1. Thread VS process

Before understanding processes and threads, let’s look at a picture:
Insert image description here

Program : A program is an ordered collection of instructions and data. It does not have any running meaning and is a static concept.

Process : A process is an execution process of executing a program. For example, QQ, music, games, etc. running on the computer are all processes one by one.

  • The process depends on the running of the program to exist. The process is dynamic and the program is static;
  • A process is an independent unit used by the operating system for resource allocation and scheduling (except for the CPU, where threads are the basic unit for processor task scheduling and execution);
  • Each process has an independent address space. The address space includes code area, data area and stack area. The address spaces between processes are isolated and do not affect each other.

Thread : A process can contain one or more threads. The thread itself basically does not own system resources, but only owns some resources that are essential for operation (such as a program counter, a set of registers and a stack). The threads in the same process Multiple threads share all resources owned by the process.

  • A thread is an entity of a process and the basic unit of CPU scheduling and dispatch . It is a basic unit that is smaller than a process and can run independently.

The difference between process and thread:

  • Inclusion relationship: A process contains one or more threads, and multiple threads in the same process share the memory resources of the process;
  • The process is the basic unit of resource allocation in the operating system, and the thread is the basic unit of processor task scheduling and execution.
  • Impact: Processes run independently, and the end of one process will not affect the normal operation of other processes; in the case of multi-threading in a process, if one of the threads crashes, the process may be terminated by the operating system;
  • Resource application: Each process has an independent address space, and switching between processes will have a large overhead; threads can be regarded as lightweight processes. Threads in the same process share the address space of the process. Each thread Each has its own independent running stack and program counter, and the overhead of switching between threads is small.

Insert image description here

Insert image description here

2. Multi-threading

2.1 Concept

Single thread : In a single-threaded Javaprogram, execution starts from the main function. Once there is loop processing, waiting for IO input and other operations in the program, the program needs to wait for the operation to end and continue running. During this waiting process, the CPU may be idle. status, resulting in lower utilization.

Insert image description here

Multi-threading : As the name suggests, multiple threads run at the same time. When a thread is blocked for some reason, the CPU turns to execute other threads to improve CPU utilization. Please see the figure below for specific expressions:

Insert image description here

2.2 How to create threads

  1. Create class A, inherit the Thread class, and override the run method

  2. Create class B to implement the Runnable interface and override the run method

  3. Create class C to implement the Callable interface

  1. Create class A, inherit the Thread class, and override the run method
package com.kevin.mutithread.demo1;

/**
 * @Author Kevin
 * @Date 16:37 2022/7/28
 * @Description TODO    
 * 		多线程的 实现方法1:继承Thread类,重写run方法来创建线程
 */
public class ThreadDemo extends Thread{
    
    
    public static void main(String[] args) {
    
    
        // 创建线程类的对象
        ThreadDemo thread = new ThreadDemo();
        // 启动线程
        thread.start();
        // 对象方法的普通调用
        // thread.run();
        
        // main线程的执行体
        for (int i = 0; i < 20; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "在执行main方法.." + i);
        }
    }

    // 重写run方法
    @Override
    public void run() {
    
    
        // 线程体需要做的事情
        for (int i = 0; i < 15; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "正在进行遍历.." + i);
        }
    }
}

It can be seen from the execution results that the main thread and the created thread thread are executed alternately;

Insert image description here

When the thread.start() method in the code is changed to thread.run(), the results are as follows:

It can be found that the thread call is:Create a thread object and call the object's start() method to start the thread and make it in a ready state. The start method will automatically call the run method of the thread class for execution; if the run() method is called directly through the object, it is a normal call of the object. , no shadow of multi-threading.

Insert image description here

  1. Create class B to implement the Runnable interface and implement the run method
package com.kevin.mutithread.demo1;

/**
 * @Author Kevin
 * @Date 18:09 2022/7/28
 * @Description  
 		多线程的实现方法2:
 *        创建类继承Runnable接口,实现run方法
 *        创建实现类对象,将其作为参数传递给new Thread(实现类对象)
 */
public class RunnableDemo implements Runnable{
    
    
    public static void main(String[] args) {
    
    
        // 1. 创建线程对象
        RunnableDemo runnableDemo = new RunnableDemo();
        // 2. 启动线程
        Thread thread = new Thread(runnableDemo);
        thread.start();
        // 3. main线程的执行体
        for (int i = 0; i < 20; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "执行main线程体..." + i);
        }
    }
    @Override
    public void run() {
    
    
        // 线程体
        for (int i = 0; i < 20; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "执行Runnable的实现方法.." + i);
        }
    }
}

Results of the:

Insert image description here
Methods that inherit the Thread class:

  • Subclasses inherit the Thread class and have multi-threading capabilities
    to start threads: subclass object.start()
    is not recommended to use to avoid the limitations of OOP single inheritance.

Implement the Runnable interface:

  • The implementation interface Runnable has multi-threading capabilities
    to start threads: pass in the target object + Thread object.start().
    This method is recommended to avoid the limitations of single inheritance, is flexible and convenient, and facilitates the same object to be used by multiple threads.
  1. Implement callable interface
package com.kevin.mutithread.demo1;

import java.util.concurrent.*;

/**
 * @Author Kevin
 * @Date 20:19 2022/7/28
 * @Description
 *          第三种创建方式:实现callable接口
 */
public class CallableDemo implements Callable<Boolean> {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 创建3个对象
        CallableDemo demo1 = new CallableDemo();
        CallableDemo demo2 = new CallableDemo();
        CallableDemo demo3 = new CallableDemo();
        // 创建执行服务 创建一个线程池,可容纳3个线程
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 提交执行
        Future<Boolean> res1 = executorService.submit(demo1);
        Future<Boolean> res2 = executorService.submit(demo1);
        Future<Boolean> res3 = executorService.submit(demo1);
        //  获取结果
        Boolean b1 = res1.get();
        Boolean b2 = res2.get();
        Boolean b3 = res3.get();
    }

    @Override
    public Boolean call() throws Exception {
    
    
        // 需要实现的线程体
        for (int i = 0; i < 20; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "正在执行..." + i);
        }
        return true;
    }
}

Results of the:
Insert image description here

The implementation of the Callable interface can customize the return value type; just understand how to create a thread.

3. Thread status

Threads also have a "life cycle" from creation to destruction. The life cycle of a thread includes 5 states.

  • Creation state : Create a thread through the Thread t = new Thread() statement. Once the thread object is created, it enters the new state;

  • Ready state : After the created thread calls the start() method, it enters the ready state, but it does not mean that it will be scheduled for execution immediately. It depends on the mood of the CPU;

  • Running state : The CPU schedules and executes the thread to enter the running state, and only then does the code block of the thread body actually execute;

  • Blocking state : When sleep, wait or synchronization lock is called, the thread enters the blocking state, which means that the code does not continue to execute. After the blocking event is released, it re-enters the ready state and waits for the CPU to reschedule execution;

  • Death state : The thread is interrupted or destroyed when it ends. Once it enters the death state, it cannot be started again.

Insert image description here

Commonly used methods in Thread class

method illustrate
setPriority(int newPriority) Change the priority of a thread
static void sleep(long millis) Sleep the currently executing thread body for the specified number of milliseconds
void join() Wait for the thread to terminate
static void yield() Pause the currently executing thread object and execute other threads
void interrupt() Interrupt thread (not recommended)
boolean isAlive() Test whether a thread is active

3.1 Thread stop (stop)

  • It is not recommended to use JDKthe provided stop() and destroy() methods. (obsolete)
  • It is recommended that the thread stop itself (use a flag bit to terminate the variable, when flag=false, the thread will be terminated)

demo demo:

package com.kevin.mutithread.demo2;

/**
 * @Author Kevin
 * @Date 21:38 2022/7/28
 * @Description
 *              演示线程的停止方式,通过标志位进行停止
 */
public class StopDemo implements Runnable {
    
    
    // 定义线程停止的标志位
    static boolean flag = true;
    public static void main(String[] args) {
    
    
        StopDemo stopDemo = new StopDemo();
        Thread thread = new Thread(stopDemo, "子线程");
        thread.start();
        // 主线程main,循环执行,当i执行到20的时候,让子线程终止
        for (int i = 0; i < 50; i++) {
    
    
            if(i == 20){
    
    
                flag  = false;
                System.out.println("子线程该停止了...");
            }
            System.out.println(Thread.currentThread().getName()+ "一直在执行..." + i);
        }
    }
    @Override
    public void run() {
    
    
        int i = 0;
        // 线程体执行内容放入由标志位控制的循环体中,
        // 当标志位不满足条件时,直接退出,结束线程
        while(flag){
    
    
            System.out.println(Thread.currentThread().getName() + "一直在执行.." + i++);
        }
    }
}

3.2 Thread sleep (sleep)

  • sleep (time) specifies the number of milliseconds for which the current thread is blocked;
  • There is an exception in sleep InterruptedException;
  • After the sleep time is reached, the thread enters the ready state;
  • Each object has a lock, and sleep does not release the lock.

sleep sleep simulation countdown:

package com.kevin.mutithread.demo2;

/**
 * @Author Kevin
 * @Date 21:48 2022/7/28
 * @Description
 *              线程休眠模拟倒计时
 */
public class SleepDemo implements Runnable{
    
    
    public static void main(String[] args) {
    
    
        // 创建线程对象
        SleepDemo sleepDemo = new SleepDemo();
        Thread thread = new Thread(sleepDemo);
        thread.start();
    }
    @Override
    public void run() {
    
    
        // 从10开始倒计时
        System.out.println("倒计时开始....");
        for (int i = 10; i >=0 ; i--) {
    
    
            try {
    
    
                Thread.sleep(1000);
                System.out.println(i);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        System.out.println("新年快乐!!!");
    }
}

3.3 Thread courtesy (yield)

  • Thread politeness (suspension) allows the currently executing thread to pause but not block;

  • Change the thread from running state to ready state, and all ready state threads are ready to snatch CPU resources;

  • Let the CPU reschedule, courtesy may not necessarily succeed, it depends on the CPU's mood.

Demo demo:

package com.kevin.mutithread.demo2;

/**
 * @Author Kevin
 * @Date 21:55 2022/7/28
 * @Description
 *          线程的礼让: 创建两个线程,当执行到礼让时,正在执行的线程处于就绪状态,就绪线程开始抢夺cpu资源
 */
public class YieldDemo implements Runnable {
    
    
    public static void main(String[] args) {
    
    
        YieldDemo yieldDemo = new YieldDemo();
        // 礼让的前提是有多个线程
        Thread thread1 = new Thread(yieldDemo, "线程--1--");
        Thread thread2 = new Thread(yieldDemo, "线程--2--");

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

    @Override
    public void run() {
    
    
        // sleep方法可以放大并发的可能性
        try {
    
    
            Thread.sleep(200);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "正在执行...");
        Thread.yield();         // 线程礼让
        System.out.println(Thread.currentThread().getName() + "线程礼让...");
    }
}

Judging from the execution results: when thread 1 reaches yield, let thread 2 start execution. When thread 2 reaches yield, it gives way. Then thread 1 grabs the execution permission of the CPU and the execution ends. .
Insert image description here

3.4 Forced execution of threads (join)

  • Join merges threads. After the execution of this thread is completed, other threads will be executed. Other threads will be blocked.
  • It can be imagined as queue jumping, thread blocking for execution.

Demo

package com.kevin.mutithread.demo2;
/**
 * @Author Kevin
 * @Date 22:06 2022/7/28
 * @Description
 *          线程的join 加塞执行,CPU 停止当前正在执行的线程,转而去先执行加塞的线程
 */
public class JoinDemo implements Runnable {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 创建线程对象
        JoinDemo joinDemo = new JoinDemo();
        Thread thread = new Thread(joinDemo);
        // main线程限制性,当i为20的时候启动加塞线程
        // 必须先启动线程,再调用线程的join方法
        for (int i = 0; i < 50; i++) {
    
    
            if (i == 20){
    
    
                thread.start();
                thread.join();
            }
            System.out.println(Thread.currentThread().getName() + "正在执行.." + i);
        }
    }

    @Override
    public void run() {
    
    
        for (int i = 0; i < 20; i++) {
    
    
            System.out.println("加塞的线程来执行了.." + i);
        }
    }
}

It can be seen that when the main thread is executed i==20, the sub-thread begins to block execution. After the execution is completed, the main thread continues to execute.

The demonstration of this code is somewhat similar to single-threaded execution. When executing the blocked sub-thread, the main thread is in a waiting state.
Insert image description here

3.5 Thread state observation (state)

The identifiers corresponding to the five thread states can be judged through Thread.State:

  • NEW: Threads that have not yet been started are in this state
  • RUNNABLE: Threads executing in the Java virtual machine are in this state
  • BLOCKED: A thread blocked waiting for a monitor lock is in this state
  • WAITING: A thread that is waiting for another thread to perform an action for the specified waiting time is in this state.
  • TIMED_WAITING: A thread that is waiting for another thread to perform an action for the specified waiting time is in this state
  • TERMINATED: The exited thread is in this state

demo demo:


package com.kevin.mutithread.demo2;

/**
 * @Author Kevin
 * @Date 9:18 2022/7/31
 * @Description
 *          观察线程的状态
 */
public class StateDemo {
    
    
    public static void main(String[] args) {
    
    
        // lambda表达式方式创建Thread
        Thread thread = new Thread(()->{
    
    
            for (int i = 0; i < 5; i++) {
    
    
                // 线程sleep休眠
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }

            System.out.println("============");
        });

        // 先观察线程thread目前的状态
        Thread.State state = thread.getState();
        System.out.println("线程创建之后未启动,此时的状态是:" + state);

        // 启动线程,再观察状态
        thread.start();
        state = thread.getState();
        System.out.println("线程已经启动了,此时的状态是:" + state);
        int i= 0;
        while (state != Thread.State.TERMINATED) {
    
    
            //只要线程不终止就一直输出状态
            try {
    
    
                Thread.sleep(200);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            //更新线程状态
            state = thread.getState();
            System.out.println("此时线程的状态是:" + state + i++);
        }


    }
}

Results of the:
Insert image description here

3.6 Thread priority (priority)

  • Java provides a thread scheduler to monitor all threads in the program that enter the ready state after being started. The thread scheduler determines which thread should be called for execution based on priority.

  • The priority of a thread is represented by a number, ranging from 1 to 10

    Thread.MIN_PRIORITY =1;		// 最小优先级
    Thread.MAX_PRIORITY =10;	// 最大优先级
    Thread.NORM_PRIORITY = 5;	// 默认优先级(正常)
    
  • Use the following methods to change or get the priority

    thread.getPriority() 			// 获取优先级
    thread.setPriority(int num)		// 设置优先级
    

demo demo:

package com.kevin.mutithread.demo2;

/**
 * @Author Kevin
 * @Date 9:34 2022/7/31
 * @Description
 *          线程的优先级
 */
public class PriorityDemo {
    
    

    public static void main(String[] args) {
    
    
        //主线程为默认优先级 5
        System.out.println(Thread.currentThread().getName() + "的优先级是-->" + Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);

        //先设置优先级再启动,二者的顺序不能颠倒,
        // 是对每个线程设置各自的优先级,线程必须先设置优先级之后再启动
        t1.start();//默认优先级
        t2.setPriority(1);
        t2.start();
        t3.setPriority(3);
        t3.start();
        //最高优先级 10
        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();
        t5.setPriority(8);
        t5.start();
    }

}
class MyPriority implements Runnable {
    
    
    @Override
    public void run() {
    
    
        try {
    
    
            Thread.sleep(100);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "的优先级是--->" + Thread.currentThread().getPriority());
    }

}

Low priority just means that the probability of getting scheduled is low, it does not mean that low priority will not be called. It all depends on the mood of the CPU and the scheduling algorithm used.

4. Thread synchronization

4.1 Concurrency

Concurrency actually means multiple threads operating on the same object at the same time. Three points are emphasized here:

  • Multiple threads (if you are the only one using this bank card, there will obviously be no problem)
  • Operate at the same time (when everyone is operating this card, concurrency problems will occur).
  • The same object (if everyone operates their own bank card, obviously no concurrency will occur)
  • Three elements of concurrency : multiple threads, simultaneous operations, and the same object.

Multiple threads operate on the same object at the same time, for example, tens of thousands of people grab 100 tickets at the same time. Once concurrency occurs, data inaccuracy may occur, which is also called thread unsafe.

4.2 Concurrency unsafe cases

  1. Unsafe Case 1: Ticket grabbing (multiple threads are grabbing tickets at the same time)
package com.kevin.mutithread.demo3;

/**
 * @Author Kevin
 * @Date 9:53 2022/7/31
 * @Description
 *          多个线程同时进行抢票
 */
public class BuyTicketDemo{
    
    
    public static void main(String[] args) {
    
    
        // 创建线程对象
        BuyTicket buyTicket = new BuyTicket();
        // 创建三个线程并启动
        new Thread(buyTicket, "学生").start();
        new Thread(buyTicket, "军人").start();
        new Thread(buyTicket, "黄牛党").start();
    }

    // 创建线程类,实现runnable接口,重写 run方法
    static class BuyTicket implements Runnable{
    
    
        // 定义票源
        int ticketCount = 10;
        // 定义线程停止标志位, 默认为true
        boolean flag = true;

        @Override
        public void run() {
    
    
            // 多线程的执行体 抢票,循环一直抢票
            while (flag){
    
    
                buyTicket();
            }
        }

        private void buyTicket() {
    
    
            //判断是否有票可买
            if(ticketCount <= 0){
    
    
                System.out.println("票已经售罄,手速慢了...");
                flag = false;
                return;
            }
            //模拟网络延时
            try {
    
    
                Thread.sleep(80);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            // 否则,直接进行抢票,票源减少
            System.out.println(Thread.currentThread().getName() + "抢到了票-->" + ticketCount--);
        }


    }
}

When there are duplicate tickets or negative numbers in the ticket grabbing results, it means it is unsafe:

Insert image description here

  1. Unsafe Case 2: Withdraw money from the account

    Two people operate an account at the same time to withdraw money

    package com.kevin.mutithread.demo3;
    
    /**
     * @Author Kevin
     * @Date 10:09 2022/7/31
     * @Description
     *          账户取钱问题,多个人同时操作同一个账户进行取钱
     */
    public class DrawingMoneyDemo {
          
          
        public static void main(String[] args) {
          
          
            Account account = new Account("老婆本儿", 60);
            Drawing myself = new Drawing(account, 30, "myself");
            Drawing girlfriend = new Drawing(account, 40, "girlfriend");
    
            myself.start();
            girlfriend.start();
        }
    
    
        // 创建取款线程类
        static class Drawing extends Thread{
          
          
            // 取哪个账户的钱
            private Account account;
            // 取多少钱
            private int drawingMoney;
            // 现在手里多少钱
            private int moneyInHand;
    
            // 构造方法
            public Drawing(Account account, int drawingMoney, String threadName){
          
          
                // 给创建的thread重命名
                super(threadName);
                this.account = account;
                this.drawingMoney = drawingMoney;
            }
    
            @Override
            public void run() {
          
          
                // 线程执行体 取钱
                // 卡内的钱还够取不
                if(account.leftMoney < this.drawingMoney){
          
          
                    System.out.println("卡内余额为:" + account.leftMoney +"\n" + Thread.currentThread().getName() + "需要取钱:" + this.drawingMoney +"余额不足,无法取钱...");
                    return;
                }
                // 模拟延迟
                try {
          
          
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
                // 取钱, 卡内余额减少
                account.leftMoney -= this.drawingMoney;
                // 手中前多了
                this.moneyInHand += this.drawingMoney;
    
                //这里this.getName()等价于Thread.currentThread().getName()
                System.out.println(this.getName() + "取钱成功,手里的钱为:" + this.moneyInHand);
                System.out.println(account.accountName + "余额为:" + account.leftMoney);
            }
        }
    
        // 创建账户类
        static class Account{
          
          
            // 账户名字
            String accountName;
            // 账户余额
            double leftMoney;
            // 构造方法
            public Account(String accountName, double leftMoney) {
          
          
                this.accountName = accountName;
                this.leftMoney = leftMoney;
            }
        }
    }
    
    

Two threads operate the same account to withdraw money at the same time. The account balance appears negative, and the thread is unsafe:

Insert image description here

4.3 Thread synchronization

​ The above two situations are due to multiple threads operating the same object at the same time ( there is no information synchronization between multiple threads ), resulting in thread unsafe conditions. To deal with this problem, thread synchronization needs to be used ; thread synchronization is actually A kind of waiting mechanism. Multiple threads that need to access this object at the same time enter the waiting pool of this object to form a queue, waiting for the previous thread to finish using it, and then the next thread can use it. Ensuring thread safety can be accomplished through "queues" and "locks".

Two conditions for thread synchronization implementation:

  • Wait for the pool to form a queue
  • The resource is locked.

Since multiple threads of the same process share the same storage space, it not only brings convenience, but also brings access conflict problems. In order to ensure the correctness of data when accessed in the method, a synchronized lock mechanism is added during access . When A thread obtains an exclusive lock on an object and occupies exclusive resources. Other threads must wait and release the lock after use.

Everything has advantages and disadvantages. Problems with the locking mechanism:

  • Holding a lock by one thread causes all other threads that need the lock to hang;
  • Under multi-thread competition, locking and releasing locks will cause more context switching and scheduling delays, causing performance problems;
  • If a high-priority thread waits for a low-priority thread to release the lock, it will cause priority inversion and cause performance problems.

Member variables in the class can restrict access rights through the private keyword, and provide get and set methods for access, so modified methods can be protected, that is, synchronized methods and synchronized code blocks .

  • Synchronized method: public synchronized void method(int args){ }

The synchronized method controls access to "objects". Each object corresponds to a lock. Each synchronized method must obtain the lock of the object that calls the method before it can be executed. Otherwise, the thread will be blocked. Once the method is executed, it will exclusively occupy the lock until The lock is released only after the method returns. Only the blocked thread can obtain the lock and continue execution.
Defect: If a large method is declared as synchronized, it will affect the efficiency. Disadvantages of the
synchronization method
Only the content that needs to be modified in the method needs to be locked. The lock is too large. Too many, a waste of resources

  • Synchronized code block synchronized (Obj){ }

Obj is called a synchronization monitor. Obj can be any object, but it is recommended to use shared resources as synchronization monitors.
There is no need to specify a synchronization monitor in the synchronization method, because the synchronization monitor of the synchronization method is this, which is the
execution of the synchronization monitor of the object itself. process

  1. The first thread accesses, locks the synchronization monitor, and executes the code in it;
  2. The second thread accessed and found that the synchronization monitor was locked and could not be accessed;
  3. After the first thread completes access, the synchronization monitor is unlocked;
  4. The second thread accesses, finds that the synchronization monitor does not have a lock, then locks and accesses

In the ticket purchase case, the modifications made:

package com.kevin.mutithread.demo3;

/**
 * @Author Kevin
 * @Date 9:53 2022/7/31
 * @Description
 *          多个线程同时进行抢票
 */
public class BuyTicketDemo{
    
    
    public static void main(String[] args) {
    
    
        // 创建线程对象
        BuyTicket buyTicket = new BuyTicket();
        // 创建三个线程并启动
        new Thread(buyTicket, "学生").start();
        new Thread(buyTicket, "军人").start();
        new Thread(buyTicket, "黄牛党").start();
    }

    // 创建线程类,实现runnable接口,重写 run方法
    static class BuyTicket implements Runnable{
    
    
        // 定义票源
        int ticketCount = 10;
        // 定义线程停止标志位, 默认为true
        boolean flag = true;

        @Override
        public void run() {
    
    
            // 多线程的执行体 抢票,循环一直抢票
            while (flag){
    
    
                buyTicket();
            }
        }

        // 买票中同步分方法是buyTicket,该方法在一直对共享变量ticketCount进行操作。
        // 对该方法使用synchronized
        private synchronized void buyTicket() {
    
    
            //判断是否有票可买
            if(ticketCount <= 0){
    
    
                System.out.println("票已经售罄, " +Thread.currentThread().getName() + "手速慢了...");
                flag = false;
                return;
            }
            //模拟网络延时
            try {
    
    
                Thread.sleep(80);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            // 否则,直接进行抢票,票源减少
            System.out.println(Thread.currentThread().getName() + "抢到了票-->" + ticketCount--);
        }


    }
}

After adding the synchronized keyword, the execution result is normal.
Insert image description here

Optimization of the money withdrawal case: When withdrawing money, the same account account operated by multiple threads needs to be synchronized:

package com.kevin.mutithread.demo3;

/**
 * @Author Kevin
 * @Date 10:09 2022/7/31
 * @Description
 *          账户取钱问题,多个人同时操作同一个账户进行取钱
 */
public class DrawingMoneyDemo {
    
    
    public static void main(String[] args) {
    
    
        Account account = new Account("老婆本儿", 60);
        Drawing myself = new Drawing(account, 30, "myself");
        Drawing girlfriend = new Drawing(account, 40, "girlfriend");

        myself.start();
        girlfriend.start();
    }

    // 创建取款线程类
    static class Drawing extends Thread{
    
    
        // 取哪个账户的钱
        private Account account;
        // 取多少钱
        private int drawingMoney;
        // 现在手里多少钱
        private int moneyInHand;
        // 构造方法
        public Drawing(Account account, int drawingMoney, String threadName){
    
    
            // 给创建的thread重命名
            super(threadName);
            this.account = account;
            this.drawingMoney = drawingMoney;
        }

        @Override
        public void run() {
    
    
            // 需要加锁的对象就是被修改的变化的对象account
            synchronized (account){
    
    
                // 卡内的钱还够取不
                if(account.leftMoney < this.drawingMoney){
    
    
                    System.out.println("卡内余额为:" + account.leftMoney +"\n" + Thread.currentThread().getName() + "需要取钱:" + this.drawingMoney +"余额不足,无法取钱...");
                    return;
                }
                // 模拟延迟
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                // 取钱, 卡内余额减少
                account.leftMoney -= this.drawingMoney;
                // 手中前多了
                this.moneyInHand += this.drawingMoney;

                //这里this.getName()等价于Thread.currentThread().getName()
                System.out.println(this.getName() + "取钱成功,手里的钱为:" + this.moneyInHand);
                System.out.println(account.accountName + "余额为:" + account.leftMoney);
            }
        }
    }
    // 创建账户类
    static class Account{
    
    
        // 账户名字
        String accountName;
        // 账户余额
        double leftMoney;
        // 构造方法
        public Account(String accountName, double leftMoney) {
    
    
            this.accountName = accountName;
            this.leftMoney = leftMoney;
        }
    }
}

Results of the:
Insert image description here

4.4 Deadlock

Multiple threads each occupy some shared resources, and wait for each other's resources to be occupied by other threads before they can run, resulting in a situation where two or more threads are waiting for each other to release resources and both stop execution. When a synchronized block holds "locks on more than two objects" at the same time, a "deadlock" problem may occur.

In short: Threads A and B each hold a lock, and at the same time they need the other's locked resources to continue execution. However, neither thread AB is willing to release the lock first, causing a deadlock.

demo demo:

package com.kevin.mutithread.demo3;

/**
 * @Author Kevin
 * @Date 10:55 2022/7/31
 * @Description
 *          死锁
 */
public class DeadLockDemo {
    
    
    public static void main(String[] args) {
    
    
        Makeup g1 = new Makeup(0, "灰姑娘");
        Makeup g2 = new Makeup(1, "白雪公主");
        g1.start();
        g2.start();
    }

    //口红类
    static class Lipstick {
    
     }
    //镜子
    static class Mirror {
    
     }
    // 线程类 化妆
    static class Makeup extends Thread {
    
    
        //需要的资源只有一份,用static来保证
        static Lipstick lipstick = new Lipstick();
        static Mirror mirror = new Mirror();
        int choice;//选择
        String girlName;//使用化妆品的人
        public Makeup(int choice, String girlName) {
    
    
            this.choice = choice;
            this.girlName = girlName;
        }
        @Override
        public void run() {
    
    
            try {
    
    
                makeup();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        //化妆方法,互相持有对方的锁,需要拿到对方的资源
        private void makeup() throws InterruptedException {
    
    
            if (choice == 0) {
    
    
                synchronized (lipstick) {
    
    //获得口红的锁
                    System.out.println(this.girlName + "获得口红的锁");
                    Thread.sleep(1000);
                    synchronized (mirror) {
    
    //一秒钟后想获得镜子
                        System.out.println(this.girlName + "获得镜子的锁");
                    }
                }
            } else {
    
    
                synchronized (mirror) {
    
    //获得镜子的锁
                    System.out.println(this.girlName + "获得镜子的锁");
                    Thread.sleep(1000);
                    synchronized (lipstick) {
    
    //一秒钟后想获得镜子
                        System.out.println(this.girlName + "获得口红的锁");
                    }
                }
            }
        }
    }
}

Both want the other's lock resources, but are unwilling to release the lock in their hands, causing a deadlock:

Insert image description here

Solution: When using lock resources, you cannot hold two locks: one lock resource cannot contain another lock.

//化妆方法,互相持有对方的锁,需要拿到对方的资源
        private void makeup() throws InterruptedException {
    
    
            if (choice == 0) {
    
    
                synchronized (lipstick) {
    
    //获得口红的锁
                    System.out.println(this.girlName + "获得口红的锁");
                    Thread.sleep(1000);
                }
                synchronized (mirror) {
    
    //一秒钟后想获得镜子
                    System.out.println(this.girlName + "获得镜子的锁");
                }
            } else {
    
    
                synchronized (mirror) {
    
    //获得镜子的锁
                    System.out.println(this.girlName + "获得镜子的锁");
                    Thread.sleep(1000);
                }
                // 把另外一把锁资源方到外面
                synchronized (lipstick) {
    
    //一秒钟后想获得镜子
                    System.out.println(this.girlName + "获得口红的锁");
                }
            }
        }

The execution result is normal:

Insert image description here

Summary:
Four necessary conditions for deadlock to occur:

  • Mutually exclusive condition : A resource can only be used by one process at a time.

  • Request and hold conditions : When a process is blocked due to requesting resources, it keeps the obtained resources.

  • Non-deprivation conditions : The resources that have been obtained by the process cannot be forcibly deprived before they are used up.

  • Circular waiting conditions : Several processes form a head-to-tail circular waiting relationship for resources.

The above four necessary conditions, as long as you find a way to break any one or more of them, you can avoid deadlock.

4.5 Lock

  • Starting from JDK 5.0, Java provides a more powerful thread synchronization mechanism - synchronization is achieved by explicitly defining synchronization lock objects. Synchronization lock uses Lock object as
  • The java.util.concurrent.locks.Lock interface is a tool for controlling multiple threads' access to shared resources. Locks provide exclusive access to shared resources. Only one thread can lock the Lock object at a time. The thread should obtain the Lock object before starting to access the shared resources.
  • The ReentrantLock class implements Lock, which has the same concurrency and memory semantics as synchronized. In implementing thread-safe control, ReentrantLock is more commonly used, and can explicitly lock and release locks.

Define lock: private final ReentrantLock lock = new ReentrantLock();

Lock: lock.lock()

Release the lock: lock.unlock()

Ticket purchase demo:

package com.kevin.mutithread.demo3;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author Kevin
 * @Date 11:10 2022/7/31
 * @Description
 *          买票案例演示lock
 */
public class LockDemo {
    
    

    public static void main(String[] args) {
    
    
        // 创建线程对象
        BuyTicketDemo.BuyTicket buyTicket = new BuyTicketDemo.BuyTicket();
        // 创建三个线程并启动
        new Thread(buyTicket, "学生").start();
        new Thread(buyTicket, "军人").start();
        new Thread(buyTicket, "黄牛党").start();
    }

    // 创建线程类,实现runnable接口,重写 run方法
    static class BuyTicket implements Runnable{
    
    
        // 定义票源
        int ticketCount = 10;
        // 定义线程停止标志位, 默认为true
        boolean flag = true;

        //定义lock锁
        private final ReentrantLock lock = new ReentrantLock();

        @Override
        public void run() {
    
    
            // 多线程的执行体 抢票,循环一直抢票
            while (flag){
    
    
                buyTicket();
            }
        }
        // 使用lock来进行控制
        private void buyTicket() {
    
    
            try{
    
    
                // 先上锁, 在try的语句块里面进行上锁,
                lock.lock();
                //判断是否有票可买
                if(ticketCount <= 0){
    
    
                    System.out.println("票已经售罄, " +Thread.currentThread().getName() + "手速慢了...");
                    flag = false;
                    return;
                }
                //模拟网络延时
                try {
    
    
                    Thread.sleep(80);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                // 否则,直接进行抢票,票源减少
                System.out.println(Thread.currentThread().getName() + "抢到了票-->" + ticketCount--);
            }finally {
    
    
                // 因为最后无论如何都要释放锁,将其放入finally中
                lock.unlock();
            }
        }
    }
}

result:
Insert image description here

4.6 synchronized VS lock

  • Lock is an explicit lock (manually open and close the lock, don't forget to close the lock) synchronized is an implicit lock, which is automatically released when it goes out of scope.
  • Lock only has code block locks, synchronized has code block locks and method locks.
  • Using Lock, the JVM will spend less time scheduling threads and perform better. And has better scalability (providing more subclasses)
  • Priority order of use:
    Lock > Synchronized code block (has entered the method body and allocated corresponding resources) > Synchronized method (outside the method body)

5. Thread collaboration

5.1 Thread communication

Application scenario: producer and consumer issues

  • Assume that only one product can be stored in the warehouse, the producer puts the produced product into the warehouse, and the consumer takes away the product from the warehouse for consumption;
  • If there is no product in the warehouse, the producer puts the product into the warehouse, otherwise stops production and waits until the product in the warehouse is picked up by the consumer;
  • If there is a product in the warehouse, the consumer can take the product away for consumption, otherwise stop consuming and wait until the product is placed in the warehouse again.

Insert image description here

problem analysis:

This is a thread synchronization problem. Producers and consumers share the same resource, and producers and consumers are interdependent and conditional on each other.

  • For producers, before producing products, they need to notify consumers to wait, and after producing products, they need to notify consumers immediately to consume;
  • For consumers, after consumption, producers must be notified that consumption has ended and new products need to be produced for consumption;
  • In the producer-consumer problem, synchronized alone is not enough; synchronized can prevent concurrent updates of the same shared resource and achieve synchronization; synchronized cannot be used to implement message passing (communication) between different threads.

Common methods for communication between threads:(Both are Object methods and can only be used in synchronized methods or synchronized code blocks, otherwise an exception will be thrown)

method name effect
wait() Indicates that the thread waits until notified by other threads. Unlike sleep, the lock will be released.
wait(long timeout) Specify the number of milliseconds to wait
notify() Wake up a waiting thread
notifyAll() Wake up all threads that call the wait() method on the same object. Threads with higher priority are scheduled first.
5.1.1 Pipe process method
  • Producer: a module that is only responsible for producing data and sending it to the buffer
  • Consumer: a module that is only responsible for taking out data from the buffer and processing it
  • Buffer: The "intermediary" between producers and consumers, the buffer that stores exchange information

demo demo:

package com.kevin.mutithread.demo4;

/**
 * @Author Kevin
 * @Date 17:48 2022/7/31
 * @Description
 *          管程法 Producer/consumer
 */
public class PCDemo {
    
    

    public static void main(String[] args) {
    
    
        // 创建缓冲区对象
        SynContainer synContainer = new SynContainer();
        // 创建线程类并启动
        new Producer(synContainer).start();
        new Consumer(synContainer).start();

    }

    // 生产者类,负责生产消息
    static class Producer extends Thread{
    
    
        SynContainer container;
        // 构造器
        public Producer(SynContainer container){
    
    
            this.container = container;
        }

        // 重写run方法
        @Override
        public void run() {
    
    
            // 生产者只负责生产方法并放入缓冲区
            for (int i = 0; i < 100; i++) {
    
    
                container.putMessage(new Product(i+1));
            }
        }

    }

    // 消费者类, 负责从缓冲区取数据
    static class Consumer extends Thread{
    
    
        SynContainer container;
        public Consumer(SynContainer container){
    
    
            this.container = container;
        }
        // 重写run方法
        @Override
        public void run() {
    
    
            // 消费者只负责消费消息
            for (int i = 0; i < 100; i++) {
    
    
                container.getMessage();
            }
        }
    }

    // 生产的东西类
    static class Product extends Thread{
    
    
        // 生产数据的编号
        int productId;
        public Product(int productId){
    
    
            this.productId = productId;
        }
    }

    // 缓冲区类
    static class SynContainer{
    
    
        // 缓冲区大小的容器来存放生产出的products
        Product[] productsList = new Product[5];
        // 容器计数器
        int count = 0;

        // 生产者将生产的商品放入容器中
        public synchronized void putMessage(Product product){
    
    
            // 如果容器满了,放不进去,需要等待先消费
            if(count == productsList.length){
    
    
                // 需要先等待消费者消费掉消息
                try {
    
    
                    this.wait();    // wait会释放对象的锁资源
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            // 否则 直接放进去
            productsList[count++] = product;
            System.out.println("生产者生产了第" + product.productId + "个产品...");

            // 产品已经放入了缓冲区,通知消费者可以消费了
            this.notifyAll();
        }

        // 消费者从缓冲区取数据消费
        public synchronized Product getMessage(){
    
    
            // 如果缓冲区没有数据,需要先等生产者放入数据
            if(count == 0){
    
    
                // 需要先等待数据放入
                try {
    
    
                    this.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            // 直接取出数据消费
            if(count > 0){
    
    
                count--;    // 数据个数和索引之间相差1, 先减一再取出
                Product product = productsList[count];
                System.out.println("消费者从缓冲区取出了第" + product.productId + "个数据进行消费处理...");
                // 处理完之后,通知生产者,可以放数据了
                this.notifyAll();
                return product;
            }else{
    
    
                // 如果缓冲区根本没有数据,就直接等待生产者放入
                this.notifyAll();
                throw new RuntimeException("缓冲区压根儿没有内容可以消费,生产者麻利儿生产...");
            }
        }

    }
}

The buffer size is set to 5. When the first 5 messages are produced, the results can be put into the buffer, and then the coordination of consumption and production begins.
Insert image description here

5.1.2 Signal light method

Complete communication between threads with the help of flag bits

  • Producer: module responsible for producing data (the module here may be: method, object, thread, process)
  • Consumer: module responsible for processing data (the module here may be: method, object, thread, process)
  • Buffer object: Producers and consumers use the same resource. There is a flag between them, which is similar to the function of a semaphore. The semaphore controls the cyclic use of producers and consumers.

In the semaphore method, the buffer has only one location, which can be understood as the producer producing a message and the consumer consuming a message, and the two alternate.

demo demo:

package com.kevin.mutithread.demo4;

/**
 * @Author Kevin
 * @Date 18:32 2022/7/31
 * @Description
 *          信号灯法: 生产者消费者交替进行,犹如交通信号灯一般,生产-消费交替进行
 */
public class PCDemo2 {
    
    
    public static void main(String[] args) {
    
    
        Message message = new Message();
        new Producer(message).start();
        new Consumer(message).start();
    }

    // 生产者负责生产消息
    static class Producer extends Thread{
    
    
        Message message;
        public Producer(Message message){
    
    
            this.message = message;
        }
        //重写run方法
        @Override
        public void run() {
    
    
            // 生产者生产消息
            for (int i = 0; i < 50; i++) {
    
    
                this.message.producerMessage(i+1);
            }
        }
    }

    // 消费者负责消费消息
    static class Consumer extends Thread{
    
    
        Message message;
        public Consumer(Message message){
    
    
            this.message = message;
        }
        //重写run方法
        @Override
        public void run() {
    
    
            // 消费者消费消息
            for (int i = 0; i < 50; i++) {
    
    
                this.message.consumerMessage();
            }
        }
    }


    // 消息处理
    static class Message{
    
    
        // message编号
        int messageId;
        // 标志位,生产者生产True, 消费者消费false
        boolean flag = true;

        // 生产
        public synchronized void producerMessage(int messageId){
    
    
            // 如果flag为false则表示 消费者正在消费,生产者需要等待
            if(!flag){
    
    
                try {
    
    
                    this.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            // 生产者生产
            System.out.println("生产者生产了第" + messageId + "个消息...");
            // 生产完之后便可通知消费者消费,标志位改变
            this.notifyAll();
            this.flag = !this.flag;         // 更新标志位
            this.messageId = messageId;     // 更新消息编号
        }

        //消费
        public synchronized void consumerMessage(){
    
    
            // 如果flag标志位true 表示需要等待生产者生产
            if(flag){
    
    
                try {
    
    
                    this.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            // 消费消息
            System.out.println("消费者消费了第" + messageId + "个消息...");
            // 同样通知生产者生产,标志位改变
            this.notifyAll();
            this.flag = !this.flag;
        }
    }
}

Producers and consumers execute alternately through signal flags:

Insert image description here

5.2 Thread pool

  • Thread pool application background

Frequent creation and destruction of threads also consumes CPU resources. If threads can be recycled, resource utilization can be effectively improved; think of a bus that can be completed in one trip from the origin to the terminal. If this mission is completed and the buses are directly destroyed and new buses are produced, wouldn't it be a huge waste of resources? Just put multiple buses at the bus station for recycling and send them out when needed. , just stop at the bus stop when not needed.

The same is true for the cyclic use of threads. Create multiple threads in advance, put them into the thread pool, obtain them directly when using them, and put them back into the pool after use. It can avoid frequent creation and destruction and realize reuse.

  • benefit:

Improve response speed (reduce the time to create new threads); reduce resource consumption (reuse threads in the thread pool, no need to create them every time); facilitate thread management (...) corePoolSize: the size of the core pool maximumPoolSize: the maximum number
of
threads
keepAliveTime :The maximum length of time a thread can last before terminating when it has no tasks

Use of thread pool

  • JDK 5.0 provides thread pool related APl: ExecutorService and Executors
  • ExecutorService: The real thread pool interface. Common subclasses ThreadPoolExecutor
  • void execute(Runnable command): Execute task/command, no return value, generally used to execute Runnable
  • Future submit(Callable task): Execute the task, have a return value, and generally execute the Callable
  • void shutdown(): close the connection pool
  • Executors: Tool class, factory class of thread pool, used to create and return different types of thread pools

Demo demonstration of thread pool:

Create a thread pool with a capacity of 3 and create 10 thread tasks for execution. The execution result should be that the three threads in the thread pool execute these 10 tasks in a loop.

package com.kevin.mutithread.demo4;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author Kevin
 * @Date 19:05 2022/7/31
 * @Description TODO
 */
public class ThreadPoolDemo {
    
    

    public static void main(String[] args) {
    
    
        //创建服务,创建线程池
        //newFixedThreadPool 参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(3);

        //创建10个线程任务
        for (int i = 0; i < 10; i++) {
    
    
            service.execute(new MyThread());
        }


        //关闭连接
        service.shutdown();
    }
    static class MyThread implements Runnable {
    
    

        @Override
        public void run() {
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在执行任务....");
        }
    }
}

Execution results:
Insert image description here
At this point, the multi-threading content has been completed!

Guess you like

Origin blog.csdn.net/weixin_43155804/article/details/126089304