Detailed explanation of Java multithreading from 0 to 1 (super detailed)

 1 Understanding of basic nouns

1.1 Thread and process

        Thread: It is the smallest unit that the operating system can perform operation scheduling . It is included in the process and is the actual operating unit in the process.

        Process: It is the basic execution entity of the program. A process has at least one thread. There can be multiple threads in a process. Applications such as QQ and WeChat are processes one by one.

1.2 Concurrency and parallelism

        Concurrency : At the same time, multiple instructions are alternately executed on a single CPU .

        Parallelism : At the same time, multiple instructions are executed simultaneously on multiple CPUs .

        Written in java such as new Thread(()->{//do something}).start(); we cannot specify whether the multithreading started is concurrent or parallel.

1.3 Multithreading

  • what is multithreading
    • Having multiple threads allows a program to do many things at the same time.
  • The role of multithreading
    • Improve program efficiency
  • Multi-threaded application scenarios
    • As long as you want multiple things to run at the same time, you need to use multithreading.
    • Such as: qq chat interface, while sending messages and receiving files

2 Implementation of multithreading

2.1 Advantages and disadvantages of the three thread implementation methods

        The implementation methods of inheriting the Thread class and implementing the Runnable interface are also different in applicable scenarios. The method of implementing the Runnable interface is more suitable for sharing and processing some data with multiple threads, such as multiple threads selling movie tickets. Because only one implementation class object of the Runnable interface is created in the following code, the member variables actually operated by multiple threads are actually the same. There is no need to decorate the shared data you want to operate with static.

  • Implementation by inheriting the Tread class
public class Test {
    public static void main(String[] args) {
        Gifts g1 = new Gifts("小王");//创建线程
        Gifts g2 = new Gifts("小吴");
        g1.start();//开启线程
        g2.start();
    }
}

public class Gifts extends Thread {
     public Gifts(String name){
        super(name);
    }
    @Override
    public void run() {
      System.out.println(getName());//输出线程名
    }
}
  • Implementation by implementing the Runnable interface

public class Test {
    public static void main(String[] args) {
        SellingTickets sellingTickets=new SellingTickets();//创建Runnable实现类
        Thread t1=new Thread(sellingTickets,"窗口一");//传入Runnable实现类,并设置线程名字
        Thread t2=new Thread(sellingTickets,"窗口二");
        t1.start();
        t2.start();
    }

}

public class SellingTickets implements Runnable{
  
    @Override
    public void run() {
        //Thread.currentThread()获取当前正在执行的线程
        System.out.println(Thread.currentThread().getName())//输出当前线程的名字
    }
}
  • Realized by using Callable interface and Future interface
public class MyCallabe implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        //返回一个0-100的随机整数
        return new Random().nextInt(100);
    }
}



public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建MyCallable的对象(表示多线程要执行的任务)
        MyCallabe mc=new MyCallabe();
        //创建FutureTask的对象(作用管理多线程的结果)
        FutureTask<Integer> futureTask=new FutureTask<>(mc);
        //创建线程对象
        Thread t1=new Thread(futureTask);
        t1.start();
        //获取多线程运行结果
        Integer result = futureTask.get();
        System.out.println(result);
        
    }
 
}

3 Common member methods for multithreading

 3.1 Method details

  • Both currentThread() and sleep(long time) are static methods, which can be called directly using the class name
  • sleep(long time),
    • Which thread executes this method will stay in this position for a corresponding time, and will not participate in the snatching of CPU resources
  • setPriority(int newPriority); set thread priority;
    • The thread priority is 5 by default, and the main thread is also 5 by default, and the priority range is 1~10. The higher the priority, the easier it is to grab the CPU execution right.
  • setDaemon(boolean on); set to daemon thread
    • When other threads end, the daemon thread will also end, not immediately. You need to give the CPU time to respond to notify this thread that you are about to end.
  • yield(); Yield the thread.
    • Make the thread enter the waiting state and give the running opportunity to the same or higher priority thread, but in fact the thread can be selected by the thread scheduler again. It is equivalent to giving up the possession of the thread, but the time for giving up cannot be set.
  • join(); insert thread
    • If the main thread uses the t1 thread to call this method, the t1 thread must be executed before the main thread can continue to execute. At this time, threads other than main and t1 are not affected.

4 locks

4.1 Scenarios for the use of locks

Because the thread execution is random, it is not to replace all the methods in the thread before changing to the next thread, but no matter where the thread runs, it may end and run the next thread. At this time, it will touch the issue of data security. As shown below:

The three threads sell 99 tickets and jointly operate the ticket data. If the 99th ticket has been sold at this time, thread 1 passes the judgment of ticket<99 and enters the method body. At this time, the thread stops suddenly, and the second thread enters. At this time, the ticket is still 99, so the second thread can still pass the judgment statement. At this time, the second thread also stops, and the third thread enters, and it can also pass the judgment statement. Then, the third thread sells the 99th ticket. Tickets, thread 2 continues to sell, and the ticket has passed ++ to become the 100th ticket, then thread 2 sells the 100th ticket, and then thread 1 sells the 101st ticket.

Then we can see from the results that there is a problem with this program. There are only 99 tickets, but 101 tickets are sold.

What we need to do at this time is to protect the security of the ticket data . When operating this data, the operation must be completed before other threads can enter the operation section. That is, the code must be completed from judgment to sale before it can be run by other threads. . And the lock can do this requirement,

 4.2 Implementation method of lock

  • Lock the code block with the synchronized keyword (synchronized code block)
//synchronizd的实现代码,卖100张票,
//count是票的数量

Object o=new Object();
@Override
    public void run() {
        while(true){
            synchronized (o) {//锁要放在循环里面,不然循环没结束,其他线程都执行不了这个代码,
                if (count == 0) {//然后就一直是这个线程在卖票
                    break;
                } else {
                    System.out.println(Thread.currentThread().getName()+"卖出了第"+(count--)+"张票");
                }
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
  • Modify the method with the synchronized keyword (synchronized method)
//同步方法实现代码
public class SellingTickets implements Runnable{
    private int count=1000;
    Object o=new Object();
    //Thread.currentThread()获取当前正在执行的线程
    @Override
    public void run() {
        while(sellingTickets(count)){
            try {//用sleep模拟真实卖票场景
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //同步方法,用synchronized关键字修饰
    public synchronized boolean sellingTickets(int count){
        if (count == 0) {
            return false;
        } else {
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(this.count--)+"张票");
            return true;
        }
    }
}
  • lock lock (directly unlock and close the lock)
//Lock锁实现代码
static Lock lock=new ReentrantLock();
    @Override
    public void run() {
        while(true){
            lock.lock();
                try {
                    if (giftQuantity<10){
                        break;
                    }else {
                        System.out.println(getName() + "送出了第" + (giftQuantity--) + "份礼物");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();//注意无论什么情况,都要确保锁能被关闭,不然其他线程获得不到锁
                }
          
        }
    }

4.3 Deadlock problem

The following code explains:

        There are two threads respectively, thread A and thread B. They execute the following code together, thread A gets lock A, thread B gets lock B, and thread A needs to get lock B if it wants to continue executing the following code, but lock B is now in the hands of thread B and needs to get it. It needs to wait for the B thread to finish releasing the B lock, and the B thread needs the A lock if it wants to continue execution, and the thread A needs to finish releasing the A lock. At this time, the two threads wait for each other to finish, and neither thread can continue to execute. This creates a deadlock problem.

//死锁代码
public class DeadLockDemo extends Thread{
    public DeadLockDemo(String name){
        super(name);
    }
    static Object lA = new Object();
    static Object lb = new Object();
    @Override
    public void run(){
        while(true){
            if("线程A".equals(getName())){
                synchronized (lA){
                    System.out.println("线程A拿到了A锁,准备拿B锁");
                    synchronized (lb){
                        System.out.println("线程A拿到了B锁,完成了一轮");
                    }
                }
            }else if("线程B".equals(getName())){
                synchronized (lb){
                    System.out.println("线程B拿到了B锁,准备拿A锁");
                    synchronized (lA){
                        System.out.println("线程B拿到了A锁,完成了一轮");
                    }
                }
            }
        }
    }
}

5 Waiting for wake-up mechanism

5.1 Examples

        There are three roles, diner, cook, and table. Two threads eaters and cooks.

        When the chef thread is running, it judges whether there is food on the table. If there is, use the wait() method to let the chef thread wait until it is awakened by other threads. If not, cook and put it on the table to wake up the diner thread.

        If there is food when the diner thread is running, eat it, wake up the chef thread to do it, and use the wait() method to make the diner thread wait if there is no food.

5.2 Common code implementation 

  • chef
//角色厨师
public class Cook extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if(Desk.count==0){
                    break;
                }else{
                    //桌子上没有食物
                    if(Desk.sign==0){
                        try {
                            //让这个线程等待。
                            Desk.lock.wait();//让当前线程与这个锁绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //桌子上有食物
                        System.out.println("食客在吃第"+Desk.count--+"碗,还能再吃"+Desk.count+"碗");
                        Desk.lock.notifyAll();//唤醒所右与这个锁绑定的线程,也就是厨师和食客,然后两者抢夺cpu,如果还是食客抢到了,则会执行上面的代码,
                        //让食客陷入次陷入等待。

                        Desk.sign=0;//修改桌子上食物的状态
                    }
                }
            }
        }
    }
}
  • diners
//角色食客
public class Foodie extends Thread{

    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if(Desk.count==0){
                    break;
                }else{
                    //桌子上没有食物
                    if(Desk.sign==1){
                        try {
                            //让这个线程等待。
                            Desk.lock.wait();//让当前线程与这个锁绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //桌子上没有食物
                        System.out.println("厨师在做第"+Desk.count+"碗");
                        Desk.lock.notifyAll();//唤醒所右与这个锁绑定的线程,也就是厨师和食客,然后两者抢夺cpu,如果还是食客抢到了,则会执行上面的代码,
                        //让食客陷入次陷入等待。
                        Desk.sign=1;//修改桌子上食物的状态
                    }
                }
            }
        }
    }
}
  •  tables and test classes
//桌子
public class Desk {
    /**
     *  一共来回多少次
     */
    public static int count = 10;

    /**
     * 标记桌子上有没有食物
     */
    public static int sign = 0;

    /**
     * 给厨师和食客的锁
     */
    public static Lock lock=new ReentrantLock();
}

//测试类
public class TreadDemo {
    public static void main(String[] args) {
        Cook cook=new Cook();
        Foodie foodie = new Foodie();
        cook.start();
        foodie.start();
    }
}

5.3 Blocking queue implementation

  5.3.1 Description of blocking queue

  • ArrayBlockingQueue: The bottom layer is an array, bounded.

  • LinkedBlockingQueue: The bottom layer is a linked list, unbounded, but not really unbounded, the maximum value is the maximum value of int.
  • put() method: When the data in the queue is full, it cannot be put in, and it will wait, which is also called blocking.
  • take() method: take out the first data, and wait when it is not available, which is also called blocking.

5.3.2 Detailed Description of Blocking Queue

        Locks are used inside the put() and take() methods to protect thread safety, and we don't need to use locks outside the methods to protect data. They are thread safe.  

5.3.3 Blocking queue implementation

//厨师
public class Cook extends Thread{
    public static ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue){
        this.queue=queue;
    }
    @Override
    public void run() {
        while (true){
            try {
                queue.put("面条");
                System.out.println("厨师做了一个面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//食客
public class Foodie extends Thread{
    public static ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue){
        this.queue=queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                String food = queue.take();
                System.out.println("我吃了"+food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//测试类
public class TreadDemo {
    public static void main(String[] args) {
        ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue<String>(1);//参数为阻塞队列的大小为1.
        Cook cook=new Cook(arrayBlockingQueue);
        Foodie foodie = new Foodie(arrayBlockingQueue);
        cook.start();
        foodie.start();
    }
}

6 thread state

 The running state in the figure is not defined in java, because the JVM in this state is handed over to the operating system for execution, and it has nothing to do with the JVM, so java does not define the running state.

The following figure is the six states formally defined in Java:

7 thread pool

7.1 Main core principles of thread pool

  1. Create a pool, the pool is empty
  2. When submitting a task, the pool will create a new thread object. After the task is executed, the thread will be returned to the pool. When submitting the task again next time, there is no need to create a new thread, and the existing thread can be reused directly.
  3. If there are no idle threads in the pool and no new threads can be created when the task is submitted, the queue will wait in line.

7.2 Code Implementation of Thread Pool

7.2.1 Create different types of thread pool objects by calling methods through the Executors thread pool tool class.

//代码实现
     /**
     * 创建一个没有上限的线程池
     */
      ExecutorService executorService = Executors.newCachedThreadPool();
        /**
         * 往线程池中提交任务
         */
        LoopTread loopTread = new LoopTread();
        executorService.submit(loopTread);
        executorService.submit(loopTread);
        executorService.submit(loopTread);
        executorService.submit(loopTread);

    /**
     * 销毁线程池
     * 此方法一般不用,因为线程池一般在项目中不进行销毁,随时会有任务
     */
        executorService.shutdown();



    /**
     * 创建一个有上限的线程池
     * 3代表这个线程最多只能同时有三个线程
     */
    ExecutorService executorService1 = Executors.newFixedThreadPool(3);
    /**
     * 提交任务
     * 提交五个任务,从控制台输出可以看出线程的复用
     */
    executorService1.submit(loopTread);
    executorService1.submit(loopTread);
    executorService1.submit(loopTread);
    executorService1.submit(loopTread);
    executorService1.submit(loopTread);

 There are five tasks in total, but only three threads are used. This is the code reuse in the thread pool.

7.2.2 Custom creation thread pool (create ThreadPoolExecutor class)

Its longest constructor has seven parameters.

  1. Number of core threads - threads that will not be deleted no matter how long they are idle in the thread pool
  2. The maximum number of threads in the thread pool - the maximum number of threads that can be created in the thread pool
  3. Idle time (numerical value) - the time that temporary threads (threads other than core threads in the thread pool) will be eliminated after being idle for as long as possible.
  4. Idle time (unit) - the time unit that the temporary thread will be eliminated after being idle for a long time. The enumeration class TimeUnit class should be used as a parameter
  5. Blocking queue - is to create a blocking queue as a parameter to pass in, that is, when the number of threads in the thread pool has reached the maximum number of threads, how many tasks are allowed to queue up to obtain threads, and the rest is handled by the scheme of parameter seven.
  6. The way to create a thread - not a new thread, but a thread factory (for example: the defaultThreadFactory method in the Executors tool class returns a thread factory)
  7. Solution when there are too many tasks to be performed - how to deal with these tasks when the waiting queue is also full (task rejection strategy).

//代码实现
   /**
     * 之前用工具类进行创建,有好多参数不能自己设置
     * 咱直接自己手动创建一个线程池,自己设置参数
     * 参数一:核心线程数量                           不能小于0
     * 参数二:最大线程数                             不能小于0,数值大于等于核心线程数量
     * 参数三:空闲临时线程最大存活时间(数值)           不能小于0
     * 参数四:空闲临时线程最大存活时间(单位)            用TimeUnit这个枚举类表示
     * 参数五:任务队列,也就是一个堵塞队列               不能为null
     * 参数六:创建线程的工厂                            不能为null
     * 参数七:任务的拒绝策略                             不能为null
     */
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        3,  // 核心线程数量
        6,              //最大线程数
        60,             //空闲临时线程最大存活时间(数值)
        TimeUnit.SECONDS,//空闲临时线程最大存活时间(单位)
        new ArrayBlockingQueue<>(3),//任务队列,也就是一个堵塞队列,也可以使用LinkedBlockingQueue这个阻塞队列
        Executors.defaultThreadFactory(),//用线程池工具类Executors创建线程的工厂
        new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略中其中一个,丢弃任务并抛出RejectedExecutionException
    );
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    threadPoolExecutor.submit(loopTread);
    }

In the above code, we set the maximum number of threads to 6, and the blocking queue can be arranged in three, which means that when there are more than 9 tasks to be executed at the same time, the tenth thread will execute the rejection strategy. The strategy I set is to discard tasks and throw Exception RejectedExecutionException. The following results can prove our conjecture.

 Line 74 happens to be the 10th task we put in the thread pool, so line 74 throws a RejectedExecutionException.

There are three critical points when a custom thread continuously submits tasks:

  • When the core thread is full, the submission team will be queued in the blocking queue
  • When the core thread is full and the blocking queue is slow, a temporary thread will be created
  • When the core thread is full, the blocking queue is full, and the temporary thread is also full, the task rejection strategy will be triggered, that is, the parameter seven

Guess you like

Origin blog.csdn.net/qq_64680177/article/details/132116521