高并发 - 1.进程和线程

news2024/12/23 18:58:16

1.进程

1.基本概念

  • 1.进程(Process)程序的一次启动执行(程序在执行过程中分配和管理资源的基本单位,操作系统资源分配的最小单位)
  • 2.程序存放在硬盘中的可执行文件,主要包括代码指令和数据
  • 3.一个进程是一个程序的一次启动和执行,是操作系统将程序装入内存,给程序分配必要的系统资源,并且开始运行程序的指令(同一个程序可以多次启动/启动多个,对应多个进程)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2.基本原理

  • 1.计算机各个组成的任务
    • 1.CPU:承担所有的计算任务
    • 2.内存:承担运行时数据的保存任务
    • 3.外存:承担数据外部永久存储的任务
    • 4.操作系统:承担计算任务调度,资源分配的任务
  • 2.一个进程由程序段数据段进程控制块三部分组成
    在这里插入图片描述
    • 1.程序段/代码段:进程的程序指令在内存中的位置,包含需要执行的指令集合
    • 2.数据段: 进程的操作数据在内存中的位置,包含需要操作的数据集合
    • 3.程序控制块(Program Control Block,PCB):包含进程的描述信息和控制信息等,是进程存在的唯一标志
      • 1.程序的描述信息
        • 1.进程ID(pid,进程标识符): 唯一,代表进程的身份
        • 2.进程名称
        • 3.进程状态
          • 1.三态模型:运行态,就绪态,阻塞态
          • 2.五态模型:新建态,终止态,运行态,就绪态,阻塞态
        • 4.进程优先级: 进程调度的依据
      • 2.进程的调度信息
        • 1.程序起始地址: 程序第一行指令的内存地址
        • 2.通信信息: 进程间通信时的消息队列
      • 3.进程的资源信息
        • 1.内存信息: 内存占用情况和内存管理所用的数据结构
        • 2.I/O设备信息: 所用的I/O设置编号及相应的数据结构
        • 3.文件句柄: 也叫文件描述符,是所打开文件的信息
      • 4.进程上下文信息(进程环境)
        • 1.执行时各种CPU寄存器的值
        • 2.当前程序计数器(PC)的值以及各种栈的值
        • 3.操作系统切换进程时当前进程被迫让出CPU,当前进程的上下文就保存在PCB结构中,供下次恢复运行时使用
  • 3.注意
    • 1.现代操作系统中,进程是并发执行的,任何进程都可以同其他进程一起执行(交替执行);
    • 2.进程内部程序段数据段有自己独立的地址空间,不同进程的地址空间是相互隔离的

3.Java程序的进程

  • 1.Java编写的程序都运行在Java虚拟机(JVM)中,当使用Java命令启动一个Java应用程序时,就会启动一个JVM进程
  • 2.JVM进程内部,所有Java程序代码都是以线程运行
  • 3.JVM找到程序的入口点main()方法,然后运行main()方法,产生一个线程,这该线程被称为主线程
  • 4.当main()方法结束后,主线程运行完成,JVM进程也随即退出

2.线程

1.基本概念

  • 1.线程(Threads):是进程代码段/程序段的一次顺序执行流程(线程是CPU调度最小单位
  • 2.一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间,系统资源
  • 3.进程仍然是操作系统资源分配最小单位
    在这里插入图片描述

2.基本原理

在这里插入图片描述

  • 1.一个标准的线程主要由三部分组成
    • 1.线程基本信息
      在这里插入图片描述
      • 1.线程ID(tid,线程标识符): 线程的唯一标识,同一个进程内不同线程的ID不会重叠
      • 2.线程名称: 方便用户识别,用户可以指定线程的名字,如果没有指定,系统会自动分配一个名称
      • 3.线程优先级: 表示线程调度的优先级,优先级越高,获取CPU的执行机会就越大
      • 4.线程状态: 表示当前线程的执行状态:新建就绪运行阻塞结束
      • 5.其他: 是否为守护线程等
    • 2.程序计数器(Program Counter,PC)
      • 1.记录着线程下一条指令的代码段内存地址,线程私有(对应JVM中的程序计数器
    • 3.栈内存
      • 1.代码段中局部变量的储存空间,线程私有(对应JVM中的虚拟机栈和本地方法栈
      • 2JDK8中每个线程在创建时默认被分配1MB大小的栈内存(注意栈溢出错误)
      • 3.栈内存和堆内存不同,栈内存不受垃圾回收器管理

3.方法执行流程

  • 1.Java中执行程序流程的重要单位是方法
  • 2.每个线程在创建时默认被分配1MB大小的栈内存
  • 3.栈内存的分配单位是栈帧,方法的每一次执行都需要为其分配一个栈帧,栈帧主要保存该方法中的局部变量操作数栈动态链接方法的返回地址以及一些附加信息
  • 4.当线程的执行流程进入方法时,JVM就会为方法分配一个对应的栈帧压入栈内存
  • 5.当线程的执行流程跳出方法时,JVM就从栈内存弹出该方法的栈帧,此时栈内存中栈帧的局部变量的内存空间就会被回收
package threadDemo;

public class StackAreaDemo {
	    public static void main(String[] args) throws InterruptedException {
	        System.out.println("当前线程ID:"+Thread.currentThread().getId());
	        System.out.println("当前线程名:"+Thread.currentThread().getName());
	        System.out.println("当前线程状态:"+Thread.currentThread().getState());
	        System.out.println("当前线程优先级:"+Thread.currentThread().getPriority());
	        System.out.println("当前线程类加载器:"+Thread.currentThread().getContextClassLoader());
	        System.out.println("当前线程堆栈帧数组:"+Thread.currentThread().getStackTrace());
	        System.out.println("当前线程线程组:"+Thread.currentThread().getThreadGroup());
	        System.out.println("当前线程此线程突然终止时调用的处理程序:"+Thread.currentThread().getUncaughtExceptionHandler());
	        System.out.println("当前线程类对象:"+Thread.currentThread().getClass());
	        int a = 1, b = 1;
	        int c = a / b;
	        anotherFun();
	        Thread.sleep(10000);
	    }

	    private static void anotherFun() {
	        int a = 1, b = 1;
	        int c = a / b;
	        anotherFun2();
	    }

	    private static void anotherFun2() {
	        int a = 1, b = 1;
	        int c = a / b;
	    }
	}

在这里插入图片描述
在这里插入图片描述

  • 1.上述代码中使用java.lang包中Thread.currentThread()静态方法,用于获取正在执行的当前线程
  • 2.上述代码定义了三个方法mainanotherFunanotherFun2,并且这三个方法有相同的三个局部变量abc
  • 3.上述代码中JVM的执行流程
    • 1.当执行main()方法时,JVMmain()方法分配一个栈帧,保存三个局部变量,然后将栈帧压入main线程的栈内存,接着执行流程进入anotherFun()方法
    • 2.执行流程进入anotherFun()方法之前JVM为其分配对应的栈帧,保存其三个局部变量,然后压入main线程的栈内存(每个方法都会有自己独立的栈帧,负责保存该方法内部的局部变量,然后压入当前线程的栈内存)
    • 3.执行流程进入anotherFun2()方法之前JVM为其分配对应的栈帧,保存其三个局部变量,然后将栈帧压入main线程的栈内存,此时main线程含有三个栈帧
    • 4.三个方法的栈帧弹出过程与压入的过程刚好相反
    • 5.anotherFun2()方法执行完成后,其栈帧从main线程的栈内存首先弹出,执行流程回到anotherFun()方法,anotherFun()方法执行完成后,其栈帧从main线程的栈内存弹出,执行流程回到main()方法,main()方法执行完成后,其栈帧弹出,此时main线程的栈内存已经全部弹空,没有剩余的栈帧,至此main线程结束
  • 4.由于栈帧的操作是后进先出,这是标准的栈操作模式,因此此存放栈帧的内存也叫作栈内存,对应JVM中得虚拟机栈和本地方法栈

4.Java程序的线程

  • 4.Java程序的进程执行过程标准的多线程的执行过程
    • 1.每当使用Java命令执行一个类时,实际上就启动了一个JVM进程
    • 2.理论上该进程的内部至少会启动两个线程,一个是main线程,另一个是GC(垃圾回收)线程
    • 3.实际上执行一个Java程序后通过Process Explorer观察线程数量远远不止两个
      在这里插入图片描述

5.核心原理

  • 1.现代操作系统提供了强大的线程管理能力,Java不需要再进行独立的线程管理和调度,而是将线程调度工作委托给操作系统的调度进程去完成
  • 2.某些系统上JVM将每个Java线程一对一地对应到操作系统地本地线程,彻底将线程调度委托给操作系统

1.线程地调度和时间片

  • 1.时间片
    • 1.由于CPU的计算频率非常高,每秒计算数十亿次,因此可以将CPU的时间从毫秒地维度进行分段,每一小段叫做一个CPU时间片
    • 2.不同的操作系统,不同的CPU,线程的CPU时间片长度都不同
    • 3.目前操作系统中主流的线程调度方式是:基于CPU时间片方式进行线程调度
    • 4.线程只有得到CPU时间片才能执行指令,没有得到时间片的线程处于就绪状态
    • 5.由于时间片非常短,在各个线程之间快速地切换,因此表现出地特征是很多个线程在同时执行或者并发执行
  • 2.CPU的主频:即CPU内核工作的时钟频率,其表示CPU数字脉冲信号发送的速度
    在这里插入图片描述
    在这里插入图片描述
    • 1.CPU的时钟频率计量单位包含
      • 1.赫(Hz)
      • 2.千赫(KHz) = 1000Hz
      • 3.兆赫(MHz) = 1000KHz
      • 4.吉赫(GHz)= 1000MHz
  • 3.线程地调度模型目前主要分为两种
    • 1.分时调度模型
    • 2.抢占式调度模型
1.分时调度模型
  • 1.系统平均分配CPU的时间片,所有线程轮流占用CPU,时间片调度的分配上所有线程人人平等
    在这里插入图片描述
2.抢占式调度模型
  • 1.系统按照线程优先级分配CPU时间片,优先级高的线程优先分配CPU时间片
  • 2.如果所有就绪线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
  • 3.由于目前大部分操作系统都是使用抢占式调度模型进行线程调度,Java的线程管理和调度是委托给操作系统完成的,因此Java的线程调度也是使用抢占式调度模型Java线程都有优先级

2.线程的优先级

  • 1.Thread类中有一个实例属性和两个实例方法,专门用于进行线程优先级相关的操作
    //属性一:
    private int priority;	//该属性保存一个Thread实例的优先级,即1~10的值
    
    //方法一:
    public final int getPriority() //获取线程优先级
    //方法二:
    public final void setPriority() //获取线程优先级
    
  • 2.Thread类中定义了三个优先级常量,priority默认是级别5,对应的类常量是NORM_PRIORITY,优先级最大值为10,最小值为1
    public static final int MIN_PRIORITY = 1;
    public static final int NORM_PRIORITY = 5;
    public static final int MAX_PRIORITY = 10;
    
  • 3.Java中使用抢占式调度模型进行线程调度,priority实例属性的优先级越高,线程获得CPU时间片的机会就越多,但非绝对
    package threadDemo;
    
    public class PriorityDemo1 {
        public static final int SLEEP_GAP = 1000;
    
        public static void main(String[] args) throws InterruptedException {
            PriorityThread[] priorityThreads = new PriorityThread[10];
            for (int i = 0; i < priorityThreads.length; i++) {
                priorityThreads[i] = new PriorityThread();
                priorityThreads[i].setPriority(i+1);
            }
            for (int i = 0; i < priorityThreads.length; i++) {
                priorityThreads[i].start();
            }
            Thread.sleep(SLEEP_GAP);
            for (int i = 0; i < priorityThreads.length; i++) {
                priorityThreads[i].stop();
            }
            for (int i = 0; i < priorityThreads.length; i++) {
                System.out.println(priorityThreads[i].getName() + "-优先级为-" + priorityThreads[i].getPriority() + "-机会值为-" + priorityThreads[i].opportunities);
            }
        }
    
        static class PriorityThread extends Thread{
            static int threadNo = 1;
    
            public PriorityThread(){
                super("thread-" + threadNo);
                threadNo++;
            }
    
            public long opportunities = 0;
    
            public void run(){
                for (int i = 0; ; i++) {
                    opportunities++;
                }
            }
        }
    }
    
    在这里插入图片描述
  • 4.注意:执行机会的获取具有随机性,优先级高的不一定获得的机会多,整体而言高优先级的线程获得的执行机会更多

3.线程的生命周期

  • 1.Java中线程的生命周期分为6种,其具体状态定义在Thread类的内部枚举类State
    在这里插入图片描述
    public enum State {
    	   /**
            * Thread state for a thread which has not yet started.
            */
            NEW,// 初始状态,一个新创建的线程,还没开始执行
    		/**
             * Thread state for a runnable thread.  A thread in the runnable
             * state is executing in the Java virtual machine but it may
             * be waiting for other resources from the operating system
             * such as processor.
             */
            RUNNABLE,//可执行状态,要么是在执行,要么是一切就绪等待执行
    
            BLOCKED,//阻塞状态,等待锁,以便进入同步块
    
      	    WAITING,//等待状态,等待其他线程去执行特定的动作,没有时间限制
    
            TIMED_WAITING,//限时等待状态,等待其他的线程去执行特定的动作,这个是在一个指定的时间范围内
    
            TERMINATED;//终止状态,线程执行结束
        }
    
  • 2.Thread类中定义了属性和方法专门用来保存和获取线程的状态
    //实例属性
    private int threadStatue;//以整数的形式保存线程的状态
    
    //实例方法
    public Thread.State getState();//返回当前线程的执行状态,一个枚举类型值
    
1.New状态
  • 1.Java源码对NEW状态的说明:创建成功但是没有调用start()方法启动的Thread线程实例都处于NEW状态
2.Runnable状态
  • 1.调用Thread实例的start()方法后,下一步如果线程获取CPU时间片开始执行,JVM将异步调用线程的run()方法执行其业务代码
  • 2.当Java线程的Thread实例的start()方法被调用后,操作系统中对应线程进入的并不是运行状态而是就绪状态,而Java线程并没有就绪状态
  • 3.并不是Thread线程实例的start()方法一经调用,其状态就从NEW状态切换RUNNABLE状态,因为此时并不意味着线程立即获取CPU时间片并且立即执行,中间需要一系列操作系统的内部操作
  • 4.run()方法被异步执行前,JVM幕后工作和操作系统的线程调度有关,Java中的线程管理是通过JNI本地调用的方式委托操作系统的线程管理API完成的
  • 5.JVM的线程状态与其幕后的操作系统线程状态之间的转换关系如下图所示
    在这里插入图片描述
  • 6.说明
    • 1.操作系统线程如果处于就绪状态,即该线程已经满足执行条件,但是还不能执行
    • 2.处于就绪状态的线程需要等待系统的调度,一旦该就绪状态的线程被系统选中,获得CPU时间片,线程就开始占用CPU执行线程的代码,此时线程的操作系统状态进入了运行状态
    • 3.操作系统中处于运行状态的线程在CPU时间用完后又回到就绪状态,等待CPU的下一次调度
    • 4.操作系统线程在就绪状态执行状态之间被系统反复地调度,这种情况会持续直到线程的代码逻辑执行完成或异常终止
    • 5.此时线程的操作系统状态又发生了改变,进入线程的TERMINATED(终止)状态
  • 7.注意
    • 1.就绪状态运行状态都是操作系统中的线程状态
    • 2.Java语言中并没有细分这两种状态,而是将这两种状态合并成同一种状态即RUNNABLE状态
    • 3.因此Thread.state枚举类中并没有定义线程的就绪状态运行状态而是只定义了RUNNABLE状态,这是Java线程状态和操作系统中线程状态不同地地方
  • 8.总结
    • 1.NEW状态的Thread实例调用了start()方法后,线程的状态将变成RUNNABLE状态
    • 2.但是线程的run()方法不一定会马上被并发执行,需要在线程获取了CPU时间片之后才真正启动并发执行
    • 3.Runnable状态之所以包含就绪和运行两种状态是因为操作系统中每个线程不会一直占有CPU时间片,所以需要在就绪和运行两种状态中反复切换直到该线程的业务代码执行完成或抛出异常
    • 4.不细分为就绪和运行是因为JVM不能决定哪个线程什么时候来运行,这取决于操作系统的时间片调度,另一方面说明JVM无法对操作系统的调度做出积极的响应
    • 5.就绪状态仅仅表示线程具备运行资格,如果没有被操作系统的调度程序挑选中,线程就永远处于就绪状态,当前线程进入就绪状态的条件包括一下几种
      • 1.调用线程的start()方法,此线程会进入就绪状态
      • 2.当前线程的执行时间片用完也会进入就绪状态
      • 3.线程睡眠sleep操作结束也是进入就绪状态
      • 4.对其他线程合入join操作结束
      • 5.等待用户输入结束
      • 6.线程争抢到对象锁Object Monitor(虽然这个线程获取到了锁,但不是立马获取时间片)
      • 7.当前线程调用yield()方法让出CPU执行权限
3.BLOCKED状态
4.WAITING状态
5.TIMED_WAITING状态
  • 1.线程处于一种特殊的等待状态:限时等待状态
  • 2.线程处于限时等待状态的操作大致分为以下几种
    • 1.Thread.sleep(int n):使当前线程进入限时等待状态,等待时间为n毫秒
    • 2.Object.wait():带时限的抢占对象的monitor
    • 3.Thread.join():带时限的线程合并
    • 4.LockSupport.parkNanos():让线程等待,时间以纳秒为单位
    • 5.LockSupport.parkUntil():让线程等待,时间可以灵活设置
6.TERMINATED状态
  • 1.处于RUNNABLE状态的线程在run()方法执行完成之后会变成终止状态TERMINATED
  • 2.如果run()方法执行过程中发生了运行时异常而没有被捕获,run()方法将被异常终止,线程也会变成TERMINATED状态

4.线程状态演示案例

package threadDemo;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

public class StatusDemo1 {
    public static final long MAX_TURN = 5;  //每个线程执行的轮次

    static int threadSeqNumber = 0; //线程编号

    static List<Thread> threadList = new ArrayList<>(); //全局的静态线程列表

    /**
     * 输出静态线程列表中每个线程的状态
     */
    private static void printThreadStatus(){
        for (Thread thread : threadList) {
            System.out.println(thread.getName() + "状态为:" + thread.getState());
        }
    }

    /**
     * 向全局的静态线程列表中加入线程
     * @param thread 线程实例
     */
    private static void addStatusThread(Thread thread){
        threadList.add(thread);
    }

    static class StatusDemoThread extends  Thread{
        public StatusDemoThread() {
            super("statusPrintThread" + (++threadSeqNumber));
            addStatusThread(this);
        }

        public void run(){
            System.out.println(getName() + "启动后状态为" + getState());
            for (int turn = 0; turn < MAX_TURN; turn++) {
                sleepMilliSeconds(500);
                printThreadStatus();
            }
            System.out.println(getName() + "- 运行结束");
        }
    }

    public static void main(String[] args){
        addStatusThread(Thread.currentThread());

        Thread sThread1 = new StatusDemoThread();
        System.out.println(sThread1.getName() + "-创建时状态为" + sThread1.getState());
        Thread sThread2 = new StatusDemoThread();
        System.out.println(sThread2.getName() + "-创建时状态为" + sThread2.getState());
        Thread sThread3 = new StatusDemoThread();
        System.out.println(sThread3.getName() + "-创建时状态为" + sThread3.getState());

        sThread1.start();
        sleepMilliSeconds(500);

        sThread2.start();
        sleepMilliSeconds(500);

        sThread3.start();
        sleepMilliSeconds(500);
    }

    public static void sleepMilliSeconds(int millisecond){
        LockSupport.parkNanos(millisecond * 1000L * 1000L);
    }
}
E:\JDK\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2020.2.1\lib\idea_rt.jar=50289:E:\IDEA\IntelliJ IDEA 2020.2.1\bin" -Dfile.encoding=UTF-8 -classpath "E:\JDK\jre\lib\charsets.jar;E:\JDK\jre\lib\deploy.jar;E:\JDK\jre\lib\ext\access-bridge-64.jar;E:\JDK\jre\lib\ext\cldrdata.jar;E:\JDK\jre\lib\ext\dnsns.jar;E:\JDK\jre\lib\ext\jaccess.jar;E:\JDK\jre\lib\ext\jfxrt.jar;E:\JDK\jre\lib\ext\localedata.jar;E:\JDK\jre\lib\ext\nashorn.jar;E:\JDK\jre\lib\ext\sunec.jar;E:\JDK\jre\lib\ext\sunjce_provider.jar;E:\JDK\jre\lib\ext\sunmscapi.jar;E:\JDK\jre\lib\ext\sunpkcs11.jar;E:\JDK\jre\lib\ext\zipfs.jar;E:\JDK\jre\lib\javaws.jar;E:\JDK\jre\lib\jce.jar;E:\JDK\jre\lib\jfr.jar;E:\JDK\jre\lib\jfxswt.jar;E:\JDK\jre\lib\jsse.jar;E:\JDK\jre\lib\management-agent.jar;E:\JDK\jre\lib\plugin.jar;E:\JDK\jre\lib\resources.jar;E:\JDK\jre\lib\rt.jar;E:\IDEA\IntelliJ IDEA 2020.2.1\Code\out\production\CoreJava_Day1_20211201;E:\IDEA\IntelliJ IDEA 2020.2.1\plugins\Kotlin\kotlinc\lib\kotlin-stdlib.jar;E:\IDEA\IntelliJ IDEA 2020.2.1\plugins\Kotlin\kotlinc\lib\kotlin-reflect.jar;E:\IDEA\IntelliJ IDEA 2020.2.1\plugins\Kotlin\kotlinc\lib\kotlin-test.jar;E:\IDEA\IntelliJ IDEA 2020.2.1\plugins\Kotlin\kotlinc\lib\kotlin-stdlib-jdk7.jar;E:\IDEA\IntelliJ IDEA 2020.2.1\plugins\Kotlin\kotlinc\lib\kotlin-stdlib-jdk8.jar" com.wd.Test.StatusDemo1
statusPrintThread1-创建时状态为NEW
statusPrintThread2-创建时状态为NEW
statusPrintThread3-创建时状态为NEW
statusPrintThread1启动后状态为RUNNABLE
main状态为:RUNNABLE
statusPrintThread1状态为:RUNNABLE
statusPrintThread2状态为:RUNNABLE
statusPrintThread3状态为:NEW
statusPrintThread2启动后状态为RUNNABLE
main状态为:TIMED_WAITING
main状态为:TIMED_WAITING
statusPrintThread1状态为:BLOCKED
statusPrintThread1状态为:RUNNABLE
statusPrintThread2状态为:BLOCKED
statusPrintThread2状态为:RUNNABLE
statusPrintThread3状态为:RUNNABLE
statusPrintThread3状态为:RUNNABLE
statusPrintThread3启动后状态为RUNNABLE
main状态为:RUNNABLE
main状态为:RUNNABLE
main状态为:RUNNABLE
statusPrintThread1状态为:BLOCKED
statusPrintThread1状态为:RUNNABLE
statusPrintThread2状态为:BLOCKED
statusPrintThread3状态为:BLOCKED
statusPrintThread2状态为:BLOCKED
statusPrintThread3状态为:RUNNABLE
statusPrintThread1状态为:BLOCKED
statusPrintThread2状态为:RUNNABLE
statusPrintThread3状态为:TIMED_WAITING
main状态为:TERMINATED
statusPrintThread1状态为:BLOCKED
statusPrintThread2状态为:RUNNABLE
statusPrintThread3状态为:BLOCKED
main状态为:TERMINATED
statusPrintThread1状态为:RUNNABLE
main状态为:TERMINATED
statusPrintThread2状态为:TIMED_WAITING
statusPrintThread1状态为:BLOCKED
statusPrintThread2状态为:TIMED_WAITING
statusPrintThread3状态为:RUNNABLE
statusPrintThread3状态为:BLOCKED
main状态为:TERMINATED
statusPrintThread1状态为:RUNNABLE
statusPrintThread2状态为:BLOCKED
statusPrintThread3状态为:BLOCKED
main状态为:TERMINATED
statusPrintThread1状态为:BLOCKED
statusPrintThread2状态为:BLOCKED
statusPrintThread3状态为:RUNNABLE
main状态为:TERMINATED
statusPrintThread1- 运行结束
statusPrintThread1状态为:RUNNABLE
statusPrintThread2状态为:RUNNABLE
statusPrintThread3状态为:TIMED_WAITING
main状态为:TERMINATED
statusPrintThread1状态为:TERMINATED
statusPrintThread2状态为:BLOCKED
main状态为:TERMINATED
statusPrintThread3状态为:RUNNABLE
statusPrintThread1状态为:TERMINATED
statusPrintThread2状态为:RUNNABLE
statusPrintThread3状态为:TIMED_WAITING
statusPrintThread2- 运行结束
main状态为:TERMINATED
statusPrintThread1状态为:TERMINATED
statusPrintThread2状态为:TERMINATED
statusPrintThread3状态为:RUNNABLE
statusPrintThread3- 运行结束
Process finished with exit code 0
  • 1.当线程新建之后,没有调用start()方法启动之前状态为NEW
  • 2.调用start()方法启动之后,其状态为RUNNABLE
  • 3.调用LockSupport.parkNanos()方法使线程等待之后,线程的状态变成了TIMED_WAITINGLockSupport.parkNanos()方法使得当前线程限时等待,LockSupport是来自JDK中的锁辅助类)
  • 4.等待结束之后,其状态又变为了RUNNABLE
  • 5.线程完成之后,它的状态变成了TERMINATED

5.Jstack工具查看线程状态

  • 1.如果CPU使用率居高不下,说明有线程一直占用着CPU资源,通过Jstack工具可以查看线程的状态
1.Jstack工具
  • 1.Jstack.exe工具是Java虚拟机自带的一种堆栈跟踪工具,其位于JDK/bin目录下
  • 2.Jstack作用:生成或导出JVM虚拟机运行实例当前时刻的线程快照(DUMP
  • 3.线程快照(DUMP):是当前JVM实例内每一个线程正在执行的方法堆栈的集合
  • 4.生成或导出线程快照的主要目的:定位线程出现长时间运行,停顿或者阻塞的原因(线程间死锁,死循环,请求外部资源导致长时间等待)
  • 5.线程出现停顿的时候可以通过Jstack查看各个线程的调用堆栈,可以查看线程在后台做什么事情或等待什么资源
2.Jstack工具使用方法
//1.通过jps查看Java进程id<pid>
jps
//2.查看某个进程的线程快照
jstack pid
  • 1.一般情况通过Jstack输出的线程信息主要包括JVM线程,用户线程等

  • 2.其中JVM线程在JVM启动时就存在,主要用于执行垃圾回收,低内存检测等后台任务,这些线程在JVM初始化时就存在,而用户线程则是在程序创建了新的线程才会生成

  • 3.注意

    • 1.实际运行中一次DUMP的信息不足以确认问题,建议产生三次DUMP信息,如果每次都指向同一个问题才能确定问题的典型性
    • 2.不同的Java虚拟机的线程导出来的DUMP信息格式是不一样的,并且同一个JVM的不同版本,DUMP信息也有差别
3.Jstack运行实例
E:\IDEA\IntelliJ IDEA 2020.2.1\Code\CoreJava_Day1_20211201>jps
15440 Jps
12648 Launcher
15272 StatusDemo1
18296

E:\IDEA\IntelliJ IDEA 2020.2.1\Code\CoreJava_Day1_20211201>jstack 15272
2022-07-31 17:20:51
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.92-b14 mixed mode):

"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001a1f2000 nid=0x3164 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001a1ee800 nid=0x510 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001a1ec000 nid=0xb3c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001a1e3800 nid=0x2170 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001a1e2800 nid=0x3c08 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001a1e1000 nid=0x2dec runnable [0x000000001a92e000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:170)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000d5e8c658> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000d5e8c658> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001a10a000 nid=0x40bc waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001a109000 nid=0x2bf8 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000018213800 nid=0x308c in Object.wait() [0x000000001a5ce000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5d08ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000000d5d08ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001820c800 nid=0x2e74 in Object.wait() [0x000000001a0cf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5d06b50> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
      at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000d5d06b50> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"main" #1 prio=5 os_prio=0 tid=0x0000000002fbe000 nid=0x154c waiting on condition [0x0000000002f9f000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:338)
        at com.wd.Test.StatusDemo1.sleepMilliSeconds(StatusDemo1.java:68)
        at com.wd.Test.StatusDemo1.main(StatusDemo1.java:58)

"VM Thread" os_prio=2 tid=0x0000000018209000 nid=0x26c8 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000003147800 nid=0x39c8 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000003149800 nid=0x33dc runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000314b000 nid=0x810 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000314c800 nid=0x36e8 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000314e800 nid=0x41fc runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000003150000 nid=0x4490 runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000003153000 nid=0x3170 runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000003155000 nid=0x3d04 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000001a2d0000 nid=0x3c48 waiting on condition

JNI global references: 16
  • 1.tid:线程实例在JVM进程中的id
  • 2.nid:线程实例在操作系统中对应的底层线程的线程id
  • 3.prio:线程实例在JVM进程中的优先级
  • 4.os_prio:线程实例在操作系统对应的底层线程的优先级
  • 5.线程状态:runnablewaiting on condition等等
  • 6.线程名称:Service Threadmain等等
  • 7.GC tack thread:垃圾回收线程,该类线程会负责进行垃圾回收,通常JVM会启动多个GC线程,GC线程的名称中,#后面的数字会累加
  • 8.VM Periodic Task ThreadJVM周期性任务调度的线程,该线程在JVM内使用得比较频繁(定期的内存监控、JVM运行状况监控)

6.基本操作

  • 1.Java线程的常用操作基本都定义在Thread类中,包括一些重要的静态方法和线程实例方法

1.线程名称的设置和获取

public class Thread implements Runnable {

	public Thread() {
       init(null, null, "Thread-" + nextThreadNum(), 0);
   }

   public Thread(Runnable target) {
       init(null, target, "Thread-" + nextThreadNum(), 0);
   }

   Thread(Runnable target, AccessControlContext acc) {
       init(null, target, "Thread-" + nextThreadNum(), 0, acc);
   }

   public Thread(ThreadGroup group, Runnable target) {
       init(group, target, "Thread-" + nextThreadNum(), 0);
   }

   public Thread(String name) {
       init(null, null, name, 0);
   }

   public Thread(ThreadGroup group, String name) {
       init(group, null, name, 0);
   }
    
   public Thread(Runnable target, String name) {
       init(null, target, name, 0);
   }
   
   public Thread(ThreadGroup group, Runnable target, String name,
                 long stackSize) {
       init(group, target, name, stackSize);
   }
   
   private void init(ThreadGroup g, Runnable target, String name,
                     long stackSize) {
       init(g, target, name, stackSize, null);
   }

   private void init(ThreadGroup g, Runnable target, String name,
                     long stackSize, AccessControlContext acc) {
       if (name == null) {
           throw new NullPointerException("name cannot be null");
       }

       this.name = name.toCharArray();

       Thread parent = currentThread();
       SecurityManager security = System.getSecurityManager();
       if (g == null) {
           /* Determine if it's an applet or not */

           /* If there is a security manager, ask the security manager
              what to do. */
           if (security != null) {
               g = security.getThreadGroup();
           }

           /* If the security doesn't have a strong opinion of the matter
              use the parent thread group. */
           if (g == null) {
               g = parent.getThreadGroup();
           }
       }

       /* checkAccess regardless of whether or not threadgroup is
          explicitly passed in. */
       g.checkAccess();

       /*
        * Do we have the required permissions?
        */
       if (security != null) {
           if (isCCLOverridden(getClass())) {
               security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
           }
       }

       g.addUnstarted();

       this.group = g;
       this.daemon = parent.isDaemon();
       this.priority = parent.getPriority();
       if (security == null || isCCLOverridden(parent.getClass()))
           this.contextClassLoader = parent.getContextClassLoader();
       else
           this.contextClassLoader = parent.contextClassLoader;
       this.inheritedAccessControlContext =
               acc != null ? acc : AccessController.getContext();
       this.target = target;
       setPriority(priority);
       if (parent.inheritableThreadLocals != null)
           this.inheritableThreadLocals =
               ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
       /* Stash the specified stack size in case the VM cares */
       this.stackSize = stackSize;

       /* Set thread ID */
       tid = nextThreadID();
   }
   
   public final synchronized void setName(String name) {
       checkAccess();
       this.name = name.toCharArray();
       if (threadStatus != 0) {
           setNativeName(name);
       }
   }

   public final String getName() {
       return new String(name, true);
   }
}
  • 1.Thread类中可以通过Thread()构造方法设置线程名称,如果没有指定则系统会自动为线程设置名称,线程将使用Thread-threadInitNumber的形式进行自动命名
  • 2.Thread类Thread()构造方法实际调用的是init()方法进行初始化,具体初始化内容如上
  • 3.线程名称也可以通过setName()方法设置,通过getName()方法取得线程名称
  • 4.注意
    • 1.线程名称一般在启动线程前设置,但也允许为运行的线程设置名称
    • 2.允许两个Thread对象有相同的名称,但应该避免
    • 3.创建线程或线程池时,需要指定有意义的线程名称,方便出错时回溯

2.线程的sleep()操作

	//使目前正在执行的线程休眠millis毫秒
   public static native void sleep(long millis) throws InterruptedException;
	//使目前正在执行的线程休眠millis毫秒,nanos纳秒
   public static void sleep(long millis, int nanos)
   throws InterruptedException {
       if (millis < 0) {
           throw new IllegalArgumentException("timeout value is negative");
       }

       if (nanos < 0 || nanos > 999999) {
           throw new IllegalArgumentException(
                               "nanosecond timeout value out of range");
       }

       if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
           millis++;
       }

       sleep(millis);
   }
   public final native void wait(long timeout) throws InterruptedException;

   public final void wait(long timeout, int nanos) throws InterruptedException {
       if (timeout < 0) {
           throw new IllegalArgumentException("timeout value is negative");
       }

       if (nanos < 0 || nanos > 999999) {
           throw new IllegalArgumentException(
                               "nanosecond timeout value out of range");
       }

       if (nanos > 0) {
           timeout++;
       }

       wait(timeout);
   }
   
   public final void wait() throws InterruptedException {
       wait(0);
   }
	package threadDemo;

	public class SleepDemo1 {
	    public static final int SLEEP_GAP = 5000;//睡眠时长5秒
	    public static final int MAX_TURN = 50;//睡眠次数,值稍微大点方便使用Jstack

	    static class SleepThread extends Thread{
	        static int threadSeqNumber = 1;

	        public SleepThread(){
	            super("sleepThread-" + threadSeqNumber);
	            threadSeqNumber++;
	        }

	        public void run(){
	            for (int i = 0; i < MAX_TURN; i++) {
	                System.out.println(getName() + ",睡眠轮次" + i);
	                try {
	                    Thread.sleep(SLEEP_GAP);
	               } catch (InterruptedException e) {
	                    System.out.println(getName() + "发生异常被中断");
	                    System.out.println(getName() + "运行结束");
	                }
	            }
	        }
	    }
	
	    public static void main(String[] args) {
	        for (int i = 0; i < 5; i++) {
	            SleepThread sleepThread = new SleepThread();
	            sleepThread.start();
	        }
	        System.out.println(Thread.currentThread().getName() + "运行结束");
	    }
	
	}
	E:\IDEA\IntelliJ IDEA 2020.2.1\Code\CoreJava_Day1_20211201>jps
	2244 StatusDemo1
	18296
	17884 Launcher
	9804 Jps

	E:\IDEA\IntelliJ IDEA 2020.2.1\Code\CoreJava_Day1_20211201>jstack 2244
	2022-07-31 18:11:28
	Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.92-b14 mixed >mode):
	
	"DestroyJavaVM" #17 prio=5 os_prio=0 tid=0x000000000102e000 nid=0x2c5c waiting on condition [0x0000000000000000]
	   java.lang.Thread.State: RUNNABLE

	"sleepThread-5" #16 prio=5 os_prio=0 tid=0x0000000019f9f800 nid=0x2bac waiting on condition [0x000000001b0ff000]
	   java.lang.Thread.State: TIMED_WAITING (sleeping)
	        at java.lang.Thread.sleep(Native Method)
	        at com.wd.Test.StatusDemo1$SleepThread.run(StatusDemo1.java:19)
	
	"sleepThread-4" #15 prio=5 os_prio=0 tid=0x0000000019f9f000 nid=0x1028 waiting on condition [0x000000001afff000]
	   java.lang.Thread.State: TIMED_WAITING (sleeping)
	        at java.lang.Thread.sleep(Native Method)
	        at com.wd.Test.StatusDemo1$SleepThread.run(StatusDemo1.java:19)

	"sleepThread-3" #14 prio=5 os_prio=0 tid=0x0000000019f9e000 nid=0x8f4 waiting on condition [0x000000001aeff000]
	   java.lang.Thread.State: TIMED_WAITING (sleeping)
	        at java.lang.Thread.sleep(Native Method)
	        at com.wd.Test.StatusDemo1$SleepThread.run(StatusDemo1.java:19)
	
	"sleepThread-2" #13 prio=5 os_prio=0 tid=0x0000000019f9d800 nid=0x3f1c waiting on condition [0x000000001adff000]
	   java.lang.Thread.State: TIMED_WAITING (sleeping)
	        at java.lang.Thread.sleep(Native Method)
	        at com.wd.Test.StatusDemo1$SleepThread.run(StatusDemo1.java:19)
	
	"sleepThread-1" #12 prio=5 os_prio=0 tid=0x0000000019f9c800 nid=0x2360 waiting on condition [0x000000001acff000]
	   java.lang.Thread.State: TIMED_WAITING (sleeping)
	        at java.lang.Thread.sleep(Native Method)
	        at com.wd.Test.StatusDemo1$SleepThread.run(StatusDemo1.java:19)

	"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x0000000019ec3800 nid=0x3d8c runnable [0x0000000000000000]
	   java.lang.Thread.State: RUNNABLE

	"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x0000000019ec0800 nid=0x4410 waiting on condition [0x0000000000000000]
	   java.lang.Thread.State: RUNNABLE

	"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x0000000019ebd800 nid=0x2788 waiting on condition [0x0000000000000000]
	   java.lang.Thread.State: RUNNABLE

	"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000019eb5800 nid=0x4428 waiting on condition [0x0000000000000000]
	   java.lang.Thread.State: RUNNABLE

	"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000019eb4800 nid=0x2610 waiting on condition [0x0000000000000000]
	   java.lang.Thread.State: RUNNABLE

	"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000000019eb3000 nid=0x3d50 runnable [0x000000001a5fe000]
	   java.lang.Thread.State: RUNNABLE
	        at java.net.SocketInputStream.socketRead0(Native Method)
	        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	        at java.net.SocketInputStream.read(SocketInputStream.java:170)
	        at java.net.SocketInputStream.read(SocketInputStream.java:141)
	        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
	        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
	        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
	        - locked <0x00000000d5e8c568> (a java.io.InputStreamReader)
	        at java.io.InputStreamReader.read(InputStreamReader.java:184)
	        at java.io.BufferedReader.fill(BufferedReader.java:161)
	        at java.io.BufferedReader.readLine(BufferedReader.java:324)
	        - locked <0x00000000d5e8c568> (a java.io.InputStreamReader)
	        at java.io.BufferedReader.readLine(BufferedReader.java:389)
	        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)
	
	"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000019e2d000 nid=0x2b80 waiting on condition [0x0000000000000000]
	   java.lang.Thread.State: RUNNABLE
	
	"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019dd9800 nid=0x18b8 runnable [0x0000000000000000]
	   java.lang.Thread.State: RUNNABLE

	"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000019dc0800 nid=0x1bb0 in Object.wait() [0x000000001a29f000]
	   java.lang.Thread.State: WAITING (on object monitor)
	        at java.lang.Object.wait(Native Method)
	        - waiting on <0x00000000d5d08ee0> (a java.lang.ref.ReferenceQueue$Lock)
	        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
	        - locked <0x00000000d5d08ee0> (a java.lang.ref.ReferenceQueue$Lock)
	        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
	        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

	"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000017e9c800 nid=0x145c in Object.wait() [0x0000000019d9f000]
	   java.lang.Thread.State: WAITING (on object monitor)
	        at java.lang.Object.wait(Native Method)
	        - waiting on <0x00000000d5d06b50> (a java.lang.ref.Reference$Lock)
	        at java.lang.Object.wait(Object.java:502)
	        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	        - locked <0x00000000d5d06b50> (a java.lang.ref.Reference$Lock)
	        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

	"VM Thread" os_prio=2 tid=0x0000000017e99000 nid=0x1a40 runnable
	
	"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002e07800 nid=0x4780 runnable

	"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002e09800 nid=0x3c50 runnable

	"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002e0b000 nid=0x1224 runnable
	
	"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002e0c800 nid=0x1530 runnable

	"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002e0e800 nid=0x26e8 runnable

	"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002e10000 nid=0x33c4 runnable
	
	"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002e13000 nid=0x3704 runnable

	"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002e15000 nid=0xeb0 runnable
	
	"VM Periodic Task Thread" os_prio=2 tid=0x0000000019f98000 nid=0xbbc waiting on condition

	JNI global references: 16
  • 1.sleep()方法的作用:让目前正在执行的线程休眠,让CPU去执行其他的任务,线程状态从执行状态变成限时阻塞状态TIMED_WAITING
  • 2.当线程睡眠满后,线程不一定会立即得到执行,因为此时CPU可能正在执行其他的任务,线程首先进入就绪状态,等待分配CPU时间片以便有机会执行
  • 3.sleep()方法定义在Thread类中,是静态本地方法,使用其他语言实现,且该方法会抛出InterruptedException受检异常
  • 4.因此如果调用sleep()方法,就必须进行异常审查,捕获InterruptException异常或再次通过方法声明存在InterruptException受检异常
  • 5.sleep()wait()方法的区别
    • 1.sleep()Thread类的静态本地方法;而wait()Object类的本地方法
    • 2.sleep()方法不会释放锁也不需要占用锁;而wait()方法必须在同步代码块同步方法中使用,即当前线程必须拥有该对象的监视器,线程释放该监视器的所有权并等待,直到另一个线程通过调用notify()notifyAll()方法通知等待该对象监视器的线程唤醒,然后线程等待直到可以重新获得监视器的所有权并继续执行,否则会抛出IllegalMonitorStateException
      在这里插入图片描述
      在这里插入图片描述

3.线程的interrupt操作

   public void interrupt() {
       if (this != Thread.currentThread())
           checkAccess();

       synchronized (blockerLock) {
           Interruptible b = blocker;
           if (b != null) {
               interrupt0();           // Just to set the interrupt flag
               b.interrupt(this);
               return;
           }
       }
       interrupt0();
   }

   public static boolean interrupted() {
       return currentThread().isInterrupted(true);
   }

   public boolean isInterrupted() {
       return isInterrupted(false);
   }

   private native boolean isInterrupted(boolean ClearInterrupted);
   
	private native void interrupt0();
@Deprecated
   public final void stop() {
       SecurityManager security = System.getSecurityManager();
       if (security != null) {
           checkAccess();
           if (this != Thread.currentThread()) {
               security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
           }
       }
       // A zero status value corresponds to "NEW", it can't change to
       // not-NEW because we hold the lock.
       if (threadStatus != 0) {
           resume(); // Wake up thread if it was suspended; no-op otherwise
       }

       // The VM can handle all thread states
       stop0(new ThreadDeath());
   }

   @Deprecated
   public final synchronized void stop(Throwable obj) {
       throw new UnsupportedOperationException();
   }
   
	private native void stop0(Object o);
  • 1.Java提供了stop()方法终止正在运行的线程,但是stop()方法已过时不建议使用,因为使用stop()方法就像突然关闭计算机电源,可能会导致程序异常
  • 2.程序中不能随便中断一个线程的,因为无法知道这个线程正在什么状态,它可能持有某把锁,强行中断线程可能导致锁不能释放的问题或线程可能在操作数据库,强行中断线程可能导致数据不一致的问题
  • 3.由于调用stop()方法来终止线程可能会产生不可预料的结果,因此不推荐调用stop()方法
  • 4.线程什么时候可以退出只有线程自己知道,Thread类的interrupt()方法可以中断线程,本质不是中断线程,而是设置中断标志
    • 1.interrupt():将调用该方法的对象所表示的线程设置一个中断标记,并不是真的停止该线程
    • 2.interrupted():获取当前线程的中断状态,并且会清除线程的状态标记,如果当前线程被中断,则为true
    • 3.isInterrupted():获取调用该方法的对象所表示的线程的中断状态,不会清除线程的状态标记,如果当前线程被中断,则为true
  • 5.当调用线程的interrupt()方法时其作用
    • 1.如果此线程处于阻塞状态,就会立马退出阻塞,并抛出InterruptException异常,线程就可以通过捕获InterruptException来做一定的处理,然后让线程退出
    • 2.如果线程被Object.wait()Thread.join()Thread.sleep()三种方法之一阻塞,此时调用该线程的interrupt()方法,该线程将抛出InterruptException中断异常,从而提早终结被阻塞状态
    • 3.如果此线程正处在运行中,线程就不受任何影响继续运行,仅仅是线程的中断标记被设置为true,程序可以在适当地位置通过调用isInterrupted()方法来查看自己是否被中断,并执行退出操作
  • 6.注意
    • 1.如果线程的interrupt()方法先被调用,然后线程开始调用阻塞方法进入阻塞状态InterruptedException异常依旧会抛出
    • 2.如果线程捕获InterruptedException异常后继续调用阻塞方法,将不再触发InterruptedException异常
    package threadDemo;
    
    import java.util.concurrent.locks.LockSupport;
    
    public class InterruptedExceptionDemo1 {
        public static final int SLEEP_GAP = 5000;//睡眠时长5秒
        public static final int MAX_TURN = 50;//睡眠次数,值稍微大点方便使用Jstack
    
        static class SleepThread extends Thread{
            static int threadSeqNumber = 1;
    
            public SleepThread(){
                super("sleepThread-" + threadSeqNumber);
                threadSeqNumber++;
            }
    
            public void run(){
                try {
                    System.out.println(getName() + "进入睡眠");
                    Thread.sleep(SLEEP_GAP);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(getName() + "发生异常被中断");
                    return;
                }
                System.out.println(getName() + "运行结束");
            }
        }
    
        public static void main(String[] args) {
            Thread sThread1 = new SleepThread();
            sThread1.start();
    
            Thread sThread2 = new SleepThread();
            sThread2.start();
    
            Thread sThread3 = new SleepThread();
            sThread3.start();
    
            sleepSeconds(2);
            sThread1.interrupt();
            sleepSeconds(2);
    		//sleepSeconds(5);
            sThread2.interrupt();
            sleepSeconds(2);
            System.out.println("程序运行结束");
    
        }
    
        public static void sleepSeconds(int millisecond){
            LockSupport.parkNanos(millisecond * 1000L * 1000L * 1000L);
        }
    }
    
    在这里插入图片描述
    在这里插入图片描述
  • 1.结果显示sleepThread-1线程在睡眠了2秒后,被主线程中断,被打断的sleepThread-1线程停止睡眠,并捕获到InterruptedException受检异常,程序在异常处理时直接返回,其后面的执行逻辑被跳过
  • 2.sleepThread-2线程在睡眠了7秒后,被主线程中断,但是在sleepThread-2线程被中断的时候,已经执行结束,所以thread2.interrupt()中断操作没有产生实质性的效果
  • 3.如果interrupt()先于阻塞执行,当进入阻塞状态后会抛出异常,可以对整段代码捕获异常,捕获到异常后会直接跳到catch()部分,可以选择打印异常信息后return线程结束或不处理线程将继续执行
  • 4.导致阻塞状态的三种方式
    • 1.sleep()
    • 2.wait()
    • 3.join()
  • 5.上述代码中interrupt本质是靠return结束处于阻塞状态的线程
  • 6.Thread.interrupt()方法并不像Thread.stop()方法中止一个正在运行的线程,其作用是设置线程的中断标志,至于线程是死亡,等待新的任务还是继续运行至下一步,取决于这个程序本身,线程可不时地检测中断标志位从而做出响应

4.线程的join操作

	//重载版本1:此方法会把当前线程变为TIMED_WAITING,直到被合并线程执行结束,或者等待被合并线程执行millis的时间
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
	//重载版本2:此方法会把当前线程变为WAITING,直到被合并线程执行结束,或者等待被合并线程执行millis+nanos的时间
    public final synchronized void join(long millis, int nanos)
	    throws InterruptedException {

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        join(millis);
    }
	//重载版本3:此方法会把当前线程变为TIMED_WAITING,直到被合并线程执行结束
    public final void join() throws InterruptedException {
        join(0);
    }
  • 1.假设有两个线程AB,线程A在执行过程中对另一个线程B的执行有依赖
  • 2.具体的依赖为:线程A需要将线程B的执行流程合并到自己的执行流程中,这就是线程合并,被动方线程B叫做被合并线程
  • 3.join()方法是Thread类的一个实例方法,有三个重载版本,需要使用被合并线程的句柄(指针,引用)去调用
  • 4.执行thread.join()代码的当前线程为合并线程thread被合并线程,合并线程进入WAITING状态让出CPU,被合并线程获得CPU进入RUNNABLE状态
  • 5.如果设置了被合并线程的执行时间millismillis+nanos,并不能保证当前线程一定会在millis时间后变为RUNNABLE,即并不意味着被合并线程在millis后会执行完毕释放CPU资源
  • 6.如果主动合并线程在等待时被中断,就会抛出InterruptedException受检异常
  • 7.调用jion()方法的语句可以理解为合并点,合并的本质是:调用wait()方法,线程A需要在合并点等待,一直等到线程B执行完成或等待超时
  • 8.将依赖线程A叫作甲方线程,被依赖线程B叫做乙方线程线程合并就是甲方线程调用乙方线程的jion()方法,执行流程上将乙方线程合并到甲方线程,甲方线程等待乙方线程执行完成后,甲方线程再继续执行
  • 9.调用join()方法的优点:比较简单;缺点:join()方法没有办法直接取得乙方线程的执行结果
    在这里插入图片描述
1.join线程的WAITING状态
  • 1.线程WAITING状态表示线程在等待被唤醒,处于WAITING状态的线程不会被分配CPU时间片
  • 2.执行以下两个操作当前线程将处于WAITING状态
    • 1.执行没有时限timeout参数的thread.join(),线程合并场景中,若线程A调用B.join()去合入B线程,则在B执行期间线程A处于WAITING状态,一直等待线程B执行完成
    • 2.执行没有时限timeout参数的object.wait(),指一个拥有object对象锁的线程,进入相应的代码临界区后,调用相应的objectwait()方法去等待其对象锁(Object Monitor)上的信号,若对象锁上没有信号,则当前线程处于WAITING状态
      在这里插入图片描述
2.join线程的TIMED_WAITING状态
  • 1.线程TIMED_WAITING状态表示在等待唤醒,处于TIMED_WAITING状态的线程不会被分配CPU时间片,需要等待被唤醒或直到等待的时限到期
  • 2.线程合入场景中,若线程A在调用B.join()操作时加入了时限参数,则在B执行期间线程A处于TIMED_WAITING状态,若B在等待时限内没有返回,则线程A结束等待TIMED_WAITING状态,恢复成RUNNABLE状态

5.线程的yield操作

public static native void yield();
  • 1.线程的yield操作作用:让目前正在执行的线程放弃当前的执行,让出CPU的执行权限,使得CPU去执行其他的线程,即让步
  • 2.处于让步状态JVM层面的线程状态仍然是RUNNABLE状态,但是该线程所对应的操作系统层面的线程从执行状态变成就绪状态
  • 3.线程yield时,线程放弃和重占CPU的时间是不确定的,可能刚刚放弃CPU马上又获得CPU执行权限,重新开始执行
  • 4.yield()方法是Thread类提供的一个静态本地方法,通过C++实现,可以让当前正在执行的线程暂停但不会阻塞该线程,只是让线程操作系统层面转入就绪状态
  • 5.注意:
    • 1.Java线程的RUNNABLE状态对应操作系统层级的线程状态包括就绪运行,严格来讲Java线程并没有就绪运行
    • 2.执行yield()方法,操作系统层面:线程从运行状态进行就绪状态,但是jvm层面:线程还是处于runnable状态,因为Java线程的runnable状态对应操作系统的就绪状态运行状态
1.yield实例
  • 1.yield操作仅能使一个线程从运行状态转到就绪状态,而不是阻塞状态
  • 2.yield不能保证使得当前正在运行的线程迅速转换到就绪状态
  • 3.即使完成了迅速切换,系统通过线程调度机制从所有就绪线程中挑选下一个执行线程时,就绪的线程有可能被选中,也有可能不被选中,其调度的过程受到其他因数(优先级)的影响
  • 4.线程调用yield之后,操作系统在重新进行线程调度时偏向于将执行机会让给优先级高的线程
    package threadDemo;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class YieldDemo1 {
        public static final int MAX_TURN = 100;//执行次数
        public static AtomicInteger index = new AtomicInteger(0);//执行编号
    
        //记录线程的执行次数
        public static Map<String,AtomicInteger> metric = new HashMap<>();
    
        //输出线程的执行次数
        private static void printMetric(){
            System.out.println("metric = " + metric);
        }
    
        static class YieldThread extends Thread{
            static int threadSeqNumber = 1;
            public YieldThread(){
                super("sleepThread-" + threadSeqNumber);
              threadSeqNumber++;
              metric.put(this.getName(),new AtomicInteger(0));
            }
          public void run(){
                for (int i = 0; i < MAX_TURN && index.get() < MAX_TURN; i++) {
                    System.out.println("线程优先级" + getPriority());
                    index.incrementAndGet();
                    metric.get(this.getName()).incrementAndGet();
                    if(i % 2 == 0){
                        Thread.yield();
                    }
                }
                printMetric();
                System.out.println(getName() + "运行结束");
            }
        }
    
        public static void main(String[] args) {
            Thread thread1 = new YieldThread();
            thread1.setPriority(Thread.MAX_PRIORITY);
            Thread thread2 = new YieldThread();
            thread2.setPriority(Thread.MIN_PRIORITY);
            System.out.println("启动线程");
            thread1.start();
            thread2.start();
        }
    }
    

6.线程的daemon操作

	/* Whether or not the thread is a daemon thread. */
  	private boolean     daemon = false;
  
	private void init(ThreadGroup g, Runnable target, String name,
                     long stackSize) {
       init(g, target, name, stackSize, null);
   }
   
	private void init(ThreadGroup g, Runnable target, String name,
                     long stackSize, AccessControlContext acc) {
       if (name == null) {
           throw new NullPointerException("name cannot be null");
       }

       this.name = name.toCharArray();

       Thread parent = currentThread();
       SecurityManager security = System.getSecurityManager();
       if (g == null) {
           /* Determine if it's an applet or not */

           /* If there is a security manager, ask the security manager
              what to do. */
           if (security != null) {
               g = security.getThreadGroup();
           }

           /* If the security doesn't have a strong opinion of the matter
              use the parent thread group. */
           if (g == null) {
               g = parent.getThreadGroup();
           }
       }

       /* checkAccess regardless of whether or not threadgroup is
          explicitly passed in. */
       g.checkAccess();

       /*
        * Do we have the required permissions?
        */
       if (security != null) {
           if (isCCLOverridden(getClass())) {
               security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
           }
       }

       g.addUnstarted();

       this.group = g;
       this.daemon = parent.isDaemon();
       this.priority = parent.getPriority();
       if (security == null || isCCLOverridden(parent.getClass()))
           this.contextClassLoader = parent.getContextClassLoader();
       else
           this.contextClassLoader = parent.contextClassLoader;
       this.inheritedAccessControlContext =
               acc != null ? acc : AccessController.getContext();
       this.target = target;
       setPriority(priority);
       if (parent.inheritableThreadLocals != null)
           this.inheritableThreadLocals =
               ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
       /* Stash the specified stack size in case the VM cares */
       this.stackSize = stackSize;

       /* Set thread ID */
       tid = nextThreadID();
   }
   
   public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

    public final boolean isDaemon() {
        return daemon;
    }
  • 1.Java中的线程分为两类
    • 1.守护线程
    • 2.用户线程
  • 2.守护线程:也称为后台线程,指程序运行过程中在后台提供某种通用服务的线程(垃圾回收线程GC
  • 3.守护线程是随着JVM进程一同结束的,即在JVM的所有线程中守护线程是最后结束的
  • 4.只要JVM实例中尚存在一个用户线程没有结束,守护线程就能执行自己的工作,只有当最后一个用户线程结束,守护线程随着JVM一同结束工作
  • 5.Thread类中有专门的属性daemon(默认为false)和方法可以设置或判断是否守护线程,同时在构造函数初始化的init()方法中会对daemon进行初始化设置
1.daemon实例
package threadDemo;

import static java.lang.Thread.currentThread;
import static java.lang.Thread.sleep;

public class DaemonDemo1 {
   public static final int SLEEP_GAP = 500;//每一轮的睡眠时长
   public static final int MAX_TURN = 4;//用户线程执行轮次

   static class DaemonThread extends Thread{
       public DaemonThread(){
           super("daemonThread");
       }
       public void run(){
           System.out.println("--daemon线程开始--");
           for (int i = 1; ; i++) {
               System.out.println("--轮次:" + i);
               System.out.println("--守护状态为:" + isDaemon());
               try {
                   sleep(SLEEP_GAP);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
   }

   public static void main(String[] args) {
       Thread daemonThread1 = new DaemonThread();
       daemonThread1.setDaemon(true);
       daemonThread1.start();

       Thread userThread = new Thread(() -> {
           System.out.println(">>用户线程开始");
           for (int i = 1; i <= MAX_TURN; i++) {
               System.out.println(">>轮次:" + i);
               System.out.println(">>"+ Thread.currentThread() +"守护状态为:" + Thread.currentThread().isDaemon());
               try {
                   sleep(SLEEP_GAP);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           System.out.println(">>用户线程结束");
       },"userThread");

       userThread.start();

       System.out.println("守护状态为:" + currentThread().isDaemon());
       System.out.println(currentThread() + "运行结束");
   }
}

在这里插入图片描述

  • 1.上述代码创建了2个线程
    • 1.一个守护线程:名称为daemonThread,通过继承Thread的方式创建
    • 2.一个用户线程,名称为userThread,通过Lambda表达式新建一个Runnable实例传入Thread构造器来创建
  • 3.程序中使用setDaemon(true)语句将daemonThread线程设置成守护线程,守护线程daemonThreadrun()方法中设置了死循环,启动后理论上永远也不会停止
  • 4.结果显示
    • 1.main线程也是一条用户线程,main线程在创建和启动daemonThreaduserThread后提前结束
    • 2.虽然main线程结束,但是其他2个线程还在继续执行,其中userThread是用户线程,所以进程还不能结束
    • 3.当用户线程userThreadrun()方法执行完成后,userThread线程执行结束,这时所有的用户线程执行完成,JVM进程随之退出
    • 4.JVM退出时守护线程daemonThread还在死循环的执行中并未结束,但是JVM会强行终止所有守护线程的执行
  • 5.当所有用户线程结束后,JVM进程会强制结束进程内所有守护线程
  • 6.注意
    • 1.守护线程必须在调用start()方法启动前将其守护状态设置为true,启动之后不能再将用户线程设置为守护线程,否则JVM会抛出一个InterruptedException异常
    • 2.守护线程存在被JVM强行终止的风险,所以守护线程中尽量不去访问系统资源(文件句柄,数据库连接等),守护线程被强行终止时可能会引发系统资源操作不负责任的中断从而导致资源不可逆的损坏
    • 3.守护线程创建的线程也是守护线程,创建之后如果通过调用setDaemon(false)将新的线程显式地设置为用户线程,新的线程可调整为用户线程
2.守护线程与用户线程的关系
  • 1.从是否为守护线程的角度对Java线程进行分类
    • 1.用户线程
    • 2.守护线程
  • 2.守护线程和用户线程的本质区别:二者与JVM虚拟机进程终止的方法不同
    在这里插入图片描述
    • 1.用户线程与JVM进程是主动关系,如果用户线程全部终止JVM虚拟机进程也随之终止
    • 2.守护线程和JVM进程是被动关系,如果JVM进程终止,所有的守护线程也随之终止

7.线程的notify操作

public final native void notify();

public final native void notifyAll();
  • 1.Java中提供了两种本地唤醒线程的操作,其位于Object
    • 1.notify()方法
      • 1.唤醒正在此对象的监视器上等待的单个线程
      • 2.如果有线程正在等待此对象,则选择其中一个线程来唤醒,这种选择是任意的,是在执行过程中自行决定的
      • 3.线程通过调用其中一个等待方法(wait)来等待对象的监视器
      • 4.如果当前线程不是此对象监视器的所有者的话会抛出 IllegalMonitorStateException,即notify()方法同wait()方法一样必须在同步代码块或同步方法中使用
      • 5.当前线程放弃对该对象的锁之前,被唤醒的线程将无法继续,被唤醒的线程将以通常的方式与任何其他线程竞争
    • 2.notifyAll()方法
      • 1.唤醒在该对象的监视器上等待的所有线程
      • 2.线程通过调用其中一个等待方法(wait)来等待对象的监视器
      • 3.如果当前线程不是此对象监视器的所有者的话会抛出 IllegalMonitorStateException,即notifyAll()方法同notify()方法一样必须在同步代码块或同步方法中使用
      • 4.当前线程放弃对该对象的锁之前,被唤醒的线程将无法继续,被唤醒的线程将以通常的方式与任何其他线程竞争
  • 2.notify()notifyAll()的区别
    • 1.notify()唤醒一个正在等待该对象锁的线程,notifyAll()唤醒所有正在等待该对象锁的线程
    • 2.notify()可能会导致死锁,而notifyAll()`则不会导致死锁
  • 3.notify()notifyAll()的区别另一种解释
    • 1.等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁
    • 2.锁池:只有获取了对象的锁,线程才能执行对象的synchronized代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待
    • 3.notify()方法随机唤醒对象的等待池中的一个线程进入锁池,而notifyAll()唤醒对象等待池中的所有线程进入锁池
  • 4.注意:javaThread类线程执行完run()方法后,一定会自动执行notifyAll()方法
1.线程间的通信
  • 1.线程通信:指当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺
  • 2.线程通信的方式
    • 1.共享内存
      • 1.volatile共享内存
      • 2.具体可参考Java SEvolatile
    • 2.消息传递
      • 1.wait/notify/notifyAll等待通知方式
      • 2.具体可参考上述内容
    • 3.管道流
      • 管道输入/输出流

8.线程的组操作

public class ThreadGroup implements Thread.UncaughtExceptionHandler {
   private final ThreadGroup parent;
   String name;
   int maxPriority;
   boolean destroyed;
   boolean daemon;
   boolean vmAllowSuspension;

   int nUnstartedThreads = 0;
   int nthreads;
   Thread threads[];

   int ngroups;
   ThreadGroup groups[];

   private ThreadGroup() {     // called from C code
       this.name = "system";
       this.maxPriority = Thread.MAX_PRIORITY;
       this.parent = null;
   }

   public ThreadGroup(String name) {
       this(Thread.currentThread().getThreadGroup(), name);
   }

   public ThreadGroup(ThreadGroup parent, String name) {
       this(checkParentAccess(parent), parent, name);
   }

   private ThreadGroup(Void unused, ThreadGroup parent, String name) {
       this.name = name;
       this.maxPriority = parent.maxPriority;
       this.daemon = parent.daemon;
       this.vmAllowSuspension = parent.vmAllowSuspension;
       this.parent = parent;
       parent.add(this);
   }
}
  • 1.Java中提供ThreadGroup类用于对线程组进行操作
  • 2.线程组作用:可以批量管理线程或线程组对象,有效地对线程或线程组对象进行组织
  • 3.线程数组、线程池、线程组区分
    • 1.线程数组是将线程放入数组中,方便做一些简单的操作
    • 2.线程池的作用是减少线程频繁创建销毁的开销
    • 3.线程组ThreadGroup所维持的线程结构更像是,提供了管理线程组的方法
      在这里插入图片描述
  • 4.线程组的方法
    • 1.getName():返回此线程组的名称
    • 2.getParent():返回此线程组的父级
    • 3.parentOf(ThreadGroup g):测试此线程组是否是其祖先线程组之一
    • 4.interrupt():中断此线程组中的所有线程
    • 5.setMaxPriority(int pri):设置组的最大优先级
    • 6.getMaxPriority():返回此线程组的最大优先级
    • 7.setDaemon(boolean daemon):更改此线程组的守护程序状态
    • 8.isDaemon():测试此线程组是否是守护线程组
    • 9.destroy():销毁此线程组及其所有子组, 此线程组必须为空,表示此线程组中的所有线程已停止,如果线程组不为空或线程组已被破坏,则抛出IllegalThreadStateException
    • 10.isDestroyed():测试此线程组是否已被破坏
    • 11.activeCount():返回此线程组及其子组中活动线程数的估计
    • 12.activeGroupCount():返回此线程组及其子组中活动组数的估计
    • 13.list():将有关此线程组的信息打印到标准输出

7.创建线程的4种方法

  • 1.Java进程中每一个线程都对应着一个Thread实例
  • 2.线程的描述信息在Thread的实例属性中得到保存,供JVM进行线程管理和调度时使用
  • 3.虽然一个进程有很多个线程,但是单CPU内核上,同一时刻只能有一个线程是正在执行,该线程也被叫做当前线程,可通过Thread.currentThread()获取
  • 4.Thread类是Java多线程编程的基础,Java中创建线程虽然有3种方式,但是3种方式都会涉及Thread

1.线程创建方法一:继承Thread类创建线程类

  • 1.如果需要并发执行业务代码则按以下步骤
    • 1.创建一个自定义类并继承Thread
    • 2.重写run()方法并将需要并发执行的业务代码编写在run()方法中
    package threadDemo;
    
    public class ThreadDemo2 {
        // 最大轮循次数
        public static final int MAX_TURN = 5;
        // 线程编号
        public static int threadNo = 1;
        // 获取当前线程名称
        public static String getCurThreadName(){
            return Thread.currentThread().getName();
        }
    
        public static void main(String[] args) {
            Thread thread = null;
            for (int i = 0; i < 2; i++) {
                thread = new DemoThread();
                thread.start();
            }
            System.out.println(getCurThreadName() + "运行结束");
        }
    
        // 静态内部类,方便访问外部类的静态成员属性和方法
        static class DemoThread extends Thread{
            public DemoThread(){
                super("DemoThread-"+threadNo++);
            }
    
            public void run(){
                for (int i = 1; i < MAX_TURN; i++) {
                    System.out.println(getName() + ",轮次:" + i);
                }
                System.out.println(getName() + "运行结束");
            }
        }
    }
    
    在这里插入图片描述
1.Thread类
public class Thread implements Runnable {
   /* Make sure registerNatives is the first thing <clinit> does. */
   private static native void registerNatives();
   static {
       registerNatives();
   }

   private volatile char  name[];
   private int            priority;
   private Thread         threadQ;
   private long           eetop;

   private boolean     single_step;

   private boolean     daemon = false;

   private boolean     stillborn = false;

   private Runnable target;

   private ThreadGroup group;

   private ClassLoader contextClassLoader;

   private AccessControlContext inheritedAccessControlContext;

   private static int threadInitNumber;

   ThreadLocal.ThreadLocalMap threadLocals = null;

   ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

	private long stackSize;

	private long nativeParkEventPointer;

	private static long threadSeqNumber;

	private volatile int threadStatus = 0;

	volatile Object parkBlocker;

	private volatile Interruptible blocker;

	private final Object blockerLock = new Object();
	
	public final static int MIN_PRIORITY = 1;

	public final static int NORM_PRIORITY = 5;

	 public final static int MAX_PRIORITY = 10;
  • 1.Java中一个线程使用一个Thread实例来描述
  • 2.Thread类的属性
    • 1.name:线程名称,
    • 2.priority:线程优先级
  • 3.Thread类的方法
    • 1.start()方法
      • 1.启动一个线程,当调用start()方法后,线程状态从New转换为Runnable,当获取到CPU时间片后会开始执行run()方法中的实际业务代码
    • 2.run()方法
      • 1.线程业务代码的入口方法,run()方法不是由用户程序来调用的,当调用start()方法启动一个线程之后,只要线程获得了CPU执行时间,便进入run()方法体去执行具体的业务代码
    • 3.总结
      • 1.start()方法用于线程的启动,run()方法作为业务代码的执行入口
      • 2.start()方法调用后线程并没有立即执行,而是等待CPU调度执行,一旦得到CPU的调度即获取了CPU的时间片后才会调用run()方法执行业务代码

2.线程创建方法二:实现Runnable接口创建线程目标类

@FunctionalInterface
public interface Runnable {
   public abstract void run();
}
/* What will be run. */
private Runnable target;
...
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}
...
public Thread(Runnable target) {
	init(null, target, "Thread-" + nextThreadNum(), 0);
}
  • 1.Thread类的run()方法实际是重写Runnable接口中run()方法,如果Runnable类型的属性target不为空,则执行target属性的run()方法
  • 2.通过含有Runnable类型的Thread构造方法传入Runnable类型target实例,然后直接通过实现Runnablerun()方法达到线程并发执行的目的
  • 3.应用场景:单继承可以使用Thread类,多实现则可以采用Runnable接口
  • 4.使用步骤
    • 1.定义一个自定义类实现Runnable接口
    • 2.重写Runnable接口中的run()抽象方法,将业务代码写入其中
    • 3.通过Thread类创建线程对象,将Runnable实例作为实际参数传递给Thread类的构造器,由Thread构造器将该Runnable实例赋值给其Runnable类型的target执行目标属性
    • 4.调用Thread实例的start()方法启动线程
    • 5.线程启动后,线程run()方法将被JVM执行,该run()方法将调用target属性的run()方法,从而完成Runnable实现类中业务代码的并发执行
    package threadDemo;
    
    import sun.misc.ThreadGroupUtils;
    
    public class ThreadDemo3 {
    	 // 最大轮循次数
    	public static final int MAX_TURN = 5;
    	 // 线程编号
        public static int threadNo = 1;
        // 获取当前线程名称
        public static String getCurThreadName(){
            return Thread.currentThread().getName();
        }
    
        public static void main(String[] args) {
            Thread thread = null;
            for (int i = 0; i < 2; i++) {
                RunTarget runTarget = new RunTarget();
                thread = new Thread(runTarget,"RunnableThread"+threadNo++);
                thread.start();
            }
            System.out.println(getCurThreadName() + "运行结束");
        }
    
        // 静态内部类,方便访问外部类的静态成员属性和方法
        static class RunTarget implements Runnable{
    
            public void run(){
                for (int i = 1; i < MAX_TURN; i++) {
                    System.out.println(getCurThreadName() + ",轮次:" + i);
                }
                System.out.println(getCurThreadName() + "运行结束");
            }
        }
    }
    
  • 5.说明
    • 1.静态内部类RunTarget执行目标类不再继承Thread线程类而是实现Runnable接口并重写run()方法
    • 2.实现Runnable接口与继承Thread类方法实现异步执行的区别
      • 1.通过实现Runnable接口的方式创建的执行目标类,如果需要访问线程的任何属性和方法,必须通过Thread.currentThread()获取当前的线程对象,通过当前线程对象间接访问
      • 2.而通过继承Thread类的方式创建的线程类,可以在子类中直接调用Thread父类的方法访问当前线程的名称、状态等信息
1.Runnable接口
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
  • 1.Runnable接口有且仅有一个抽象run()方法
  • 2.Runnable接口上声明了@FunctionalInterface注解,该注解标记Runnable接口是一个函数式接口,因此接口实现时可以使用Lambda表达式提供匿名实现
  • 3.将业务逻辑代码编写在Runnable实现类的run()的实现版本中,当Runnble实例传入Thread实例的target属性后,Runnable接口的run()的实现版本将被异步调用
  • 4.如果直接调用Thread.run()方法就变成同步方法,而不是异步处理方式
  • 5.通过调用线程类的start()方法启动一个线程,使线程处于就绪状态即可以被JVM来调度执行,调用过程中JVM通过调用线程类的run()方法执行实际的业务代码,当run()方法结束后线程就会终止
  • 6.如果直接调用线程类的run()方法会被当做一个普通的函数调用,程序中任然只有主线程这一个线程,即start()方法能够异步的调用run()方法,但直接调用run()方法却是同步的无法达到多线程的目的
2.创建Runnable线程目标类的两种方式
  • 1.实现Runnable接口创建线程目标类除了直接实现Runnable接口外,还有两种比较优雅的代码组织方式
    • 1.通过匿名内部类创建Runnable线程目标类
    //如果target实现类是一次性类,可以使用匿名实例的形式
    package threadDemo;
    
    public class ThreadDemo4 {
        // 最大轮循次数
        public static final int MAX_TURN = 5;
        // 线程编号
        public static int threadNo = 1;
        // 获取当前线程名称
        public static String getCurThreadName(){
            return Thread.currentThread().getName();
        }
    
        public static void main(String[] args) {
            Thread thread = null;
            for (int i = 0; i < 2; i++) {
                // 通过编写匿名类的实现代码直接创建一个Runnable类型的匿名target执行目标对象
                thread = new Thread(new Runnable() {
                    public void run() {
                        for (int i = 1; i < MAX_TURN; i++) {
                            System.out.println(getCurThreadName() + ",轮次:" + i);
                        }
                        System.out.println(getCurThreadName() + "运行结束");
                    }
                },"RunnableThread" + threadNo++);
                thread.start();
            }
            System.out.println(getCurThreadName() + "运行结束");
        }
    }
    
    • 2.通过Lambda表达式创建Runnable线程目标类
    package threadDemo;
    
    public class ThreadDemo5 {
        // 最大轮循次数
        public static final int MAX_TURN = 5;
        // 线程编号
        public static int threadNo = 1;
        // 获取当前线程名称
        public static String getCurThreadName(){
            return Thread.currentThread().getName();
        }
    
        public static void main(String[] args) {
            Thread thread = null;
            for (int i = 0; i < 2; i++) {
                // 通过编写匿名类的实现代码直接创建一个Runnable类型的匿名target执行目标对象
                thread = new Thread(() -> {
                    for (int j = 1; j < MAX_TURN; j++) {
                        System.out.println(getCurThreadName() + ",轮次:" + j);
                    }
                    System.out.println(getCurThreadName() + "运行结束");
                },"RunnableThread" + threadNo++);
                thread.start();
            }
            System.out.println(getCurThreadName() + "运行结束");
        }
    }
    
3.Runnable接口创建线程目标类的优缺点
  • 1.缺点
    • 1.所创建类并不是线程类,而是线程的target执行目标类,需要将其实例作为参数传入线程类的构造器,才能创建真正的线程
    • 2.如果访问当前线程的属性,不能直接访问Thread实例方法,必须通过Thread.currentThread()获取当前线程实例,才能访问和控制当前线程
  • 2.优点:
    • 1.可以避免由于Java单继承带来的局限性,如果异步逻辑类已经继承了一个基类,就没有办法再继承Thread类,所以在已经存在继承关系的情况下,只能使用实现Runnable接口的方式
    • 2.逻辑和数据更好分离,通过实现Runnable接口的方式创建多线程更适合同一个资源被多段业务逻辑并行处理的场景
    • 3.同一个资源被多个线程逻辑异步并行处理的场景中,通过实现Runnable接口的方式设计多个target执行目标类可以更加方便,清晰地将执行逻辑和数据存储分离,更好地体现面向对象的设计思想
4.线程安全问题
  • 1.通过继承Thread类实现多线程能更好地做到多个线程并发地完成各自的任务,访问各自的数据资源
  • 2.通过实现Runnable接口实现多线程能更好地做到多个线程并发地完成同一个任务,访问同一份数据资源
    • 1.创建多个线程但每个线程共享同一个Runnable接口,从而对该对象的操作也是共享的
    • 2.需要多线程操作同一个对象实例时可以采用这种方法,同时需要注意数据一致性,这样可以将线程逻辑和业务数据进行有效的分离
  • 3.通过实现Runnable接口实现多线程时,如果数据资源存在多线程共享的情况,那么数据共享资源需要使用原子类型而非普通数据类型或需要进行线程的同步控制,以保证对共享数据操作时不会出现线程安全问题
    package threadDemo;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class ThreadDemo6 {
        public static final int MAX_AMOUNT = 50; // 商品数量
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("商店版本的销售");
            for (int i = 1; i <= 3; i++) {
                Thread thread = new StoreGoods("店员" + i);
                thread.start();
            }
            System.out.println("商场版本的销售");
            MallGoods mallGoods = new MallGoods();
            for (int i = 1; i <= 4; i++) {
                Thread thread =  new Thread(mallGoods,"商场销售员" + i);
                thread.start();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束.");
        }
    
        // 商店商品类(销售线程类)一个商品一个销售线程,每个线程异步销售4次
        static class StoreGoods extends Thread{
            private int goodsAmount = MAX_AMOUNT;
    
            StoreGoods(String name){
                super(name);
            }
    
            @Override
            public void run(){
                for (int i = 0; i <= MAX_AMOUNT; i++) {
                    if(this.goodsAmount > 0){
                        System.out.println(getName() + "卖出一件,还剩:" + (--goodsAmount));
                    }
                }
                System.out.println(getName() + "运行结束");
            }
        }
    
        // 商场商品类(target销售线程的目标类),一个商品最多销售4次,可以多人销售
        static class MallGoods implements Runnable{
            // 多人销售可能导致数据出错,使用原子数据类型保障数据安全
            private final AtomicInteger goodsAmount = new AtomicInteger(MAX_AMOUNT);
    
            @Override
            public void run() {
                for (int i = 0; i <= MAX_AMOUNT; i++) {
                    test();
                }
                System.out.println(Thread.currentThread().getName() + "运行结束");
            }
    
            // 注意需要加同步锁,否则会同时有多人同时销售同一个商品,导致数据错误
            public synchronized void test(){
                if (this.goodsAmount.get() > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出一件,还剩:" + (goodsAmount.decrementAndGet()));
                }
            }
        }
    }
    

3.线程创建方法三:使用Callable和FutureTask创建线程

  • 1.继承Thread类或实现Runnable接口两种方式创建线程类共同的缺点:不能获取异步执行的结果
  • 2.因为两者本质都是重写run()方法,而run()方法不支持返回值
  • 3.为了解决异步执行的结果问题,Java1.5版本之后提供了一种新的多线程创建方式
    • 1.通过Callable接口和FutureTask类相结合创建线程
1.Callable接口
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
  • 1.Callable接口位于java.util.concurrent包中
  • 2.Callable接口是一个泛型接口也是一个函数式接口
  • 3.其唯一抽象方法call()有返回值,还有一个Exception受检异常声明,允许方法的实现版本的内部异常直接抛出不予捕获
  • 4.Callable接口和Runnable接口的区别
    • 1.Runnable接口的唯一抽象方法run()没有返回值也没有受检异常的异常声明,而Callable接口的唯一抽象方法call()方法有返回值,并且声明了受检异常
    • 2.Runnale接口实例作为Thread线程实例的target进行使用,而Callable接口实例不可作为Thread线程实例的target进行使用
      • 因为Thread类的target属性的类型为Runnable,而Callable接口与Runnable接口时间没有任何继承关系,所以Callable接口实例没办法作为Thread线程实现的target使用
  • 5.Callable接口与Thread线程之间存在一个桥梁是RunnableFuture接口
2.Future接口
public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);// 取消异步执行

    boolean isCancelled();// 判断异步任务是否在完成前被取消。如果任务完成前被取消,就返回true

    boolean isDone();// 判断异步任务是否执行完成

    V get() throws InterruptedException, ExecutionException;// 获取异步任务完成后的执行结果

    V get(long timeout, TimeUnit unit)
     throws InterruptedException, ExecutionException, TimeoutException;// 设置时限,获取异步任务完成后的执行结果
}
  • 1.Future接口位于java.util.concurrent包中,是一个泛型接口
  • 2.Future接口至少提供了三大功能
    • 1.能够取消异步执行中的任务
    • 2.判断异步任务是否执行完成
    • 3.获取异步任务完成后的执行结果
  • 3.Future提供对异步任务进行交互操作的接口,JDK提供了一个默认的实现类FutureTask
  • 4.Futrue接口抽象方法
    • 1.get()方法
      • 1.获取异步任务执行的结果,该方法的调用是阻塞性的
      • 2.如果异步任务没有执行完成,异步结果获取线程(调用线程)会一直被阻塞,一直阻塞到异步任务执行完成,其异步结果返回给调用线程
    • 2.get(long timeout, TimeUnit unit)方法
      • 1.调用线程阻塞性地获取异步任务执行地结果,但调用线程会有一个阻塞时长限制,不会无限制地阻塞和等待
      • 2.如果其阻塞时间超过设定时间timeout,该方法将抛出异常,调用线程可捕获此异常
3.RunnableFuture接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
	  void run();
}
  • 1.RunnableFuture接口位于java.util.concurrent包中,是一个泛型接口
  • 2.RunnableFuture接口继承了RunnableFuture两个接口从而实现两个目标
    • 1.可以作为Thread线程实例的targetRunnableFuture继承Runnable接口,保证其实例可作为Thread线程实例的target
    • 2.可以获取异步执行的结果:RunnableFuture继承Future接口,保证其实例可获取未来的异步执行结果
4.FutureTask类
public class FutureTask<V> implements	RunnableFuture<V> {
    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

    private Callable<V> callable;
    private Object outcome;
    private volatile Thread runner;
    private volatile WaitNode waiters;

    @SuppressWarnings("unchecked")
    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of >	callable
    }

    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

    public boolean isCancelled() {
        return state >= CANCELLED;
    }

    public boolean isDone() {
        return state != NEW;
    }

    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&
          (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }

    protected void done() { }

    public void run() {
        if (state != NEW ||
         !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
               handlePossibleCancellationInterrupt(s);
        }
    }

	protected void set(V v) {
       if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
       }
 }

在这里插入图片描述

  • 1.FutureTask类是RunnableFuture接口的实现类,提供对异步任务操作的具体实现
  • 2.FutureTask类中有一个Callable类型的callable属性,用来保存并发执行的Callable类型的任务,并且callable属性需要在FutureTask实例构造时初始化
  • 3.FutureTask类实现了Runnable接口,其run()方法的中会执行callable属性的call()方法
  • 4.FuntureTask类中有一个Object类型的outcome属性,用来保存callable属性的call()方法的异步执行结果
  • 5.FutureTask类的run()方法执行callable属性的call()方法之后,将其结果保存在outcome实属性中,通过get()方法获取
5.Callable和FutrueTask创建线程的具体步骤
  • 1.通过FutureTask类和Callable接口联合使用可以创建能够获取异步执行结果的线程
    • 1.创建一个Callable接口的自定义实现类并实现其Call方法,可以有返回值
    • 2.使用Callable实现类的实例构建一个FutureTask实例
    • 3.使用FutureTask实例作为Thread构造器的target入参,构造新的Thread线程实例
    • 4.调用Thread实例的start()方法启动新线程,启动新线程的run()方法并发执行
    • 5.其内部的执行过程:启动Thread实例的run()方法并发执行后会执行FutureTask实例的run()方法,最终会并发执行Callable实现类的call()方法
    • 6.调用FutureTask对象的get()方法阻塞性地获得并发线程地执行结果
6.Callable和FutrueTask创建线程的实例
package threadDemo;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo7 {
    public static final int MAX_TURN = 5;
    public static final int COMPUTE_TIMES = 100000000;

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ReturnableTask returnableTask = new ReturnableTask();
        FutureTask<Long> longFutureTask = new FutureTask<>(returnableTask);
        Thread thread = new Thread(longFutureTask, "returnableThread");
        thread.start();
        Thread.sleep(500);
        System.out.println(Thread.currentThread().getName() + "让子弹飞会!");
        System.out.println(Thread.currentThread().getName() + "做一点自己地事情!");
        for (int i = 0; i < COMPUTE_TIMES/2; i++) {
            int j = i * 10000;
        }
        System.out.println(Thread.currentThread().getName() + "获取并发任务地执行过程");
        System.out.println(thread.getName() + "线程占用时间:" + longFutureTask.get());
        System.out.println(Thread.currentThread().getName() + "运行结束.");
    }

    static class ReturnableTask implements Callable<Long> {

        @Override
        public Long call() throws Exception {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "线程运行开始.");
            Thread.sleep(1000);
            for (int i = 0; i < COMPUTE_TIMES; i++) {
                int j = i * 1000;
            }
            long usedTime = System.currentTimeMillis() - startTime;
            System.out.println(Thread.currentThread().getName() + "线程运行结束.");
            return usedTime;
        }
    }
}
  • 1.上述代码中创建了两个线程
    • 1.执行main()方法的主线程
    • 2.通过thread.start()方法启动的returnableThread线程,该线程是以FutureTask实例作为targetThread线程
  • 2.上述代码执行流程
    • 1.首先执行main主线程
    • 2.然后main主线程通过thread.start()方法启动returnableThread线程
    • 3.main主线程继续执行,ruturnableThread线程开始并发执行
    • 4.returnableThread线程首先执行thread.run()方法,然后会执行到其targetfutureTask实例)的run()方法
    • 5.接着futureTask.run()方法中执行futureTaskcallable属性的call()方法
    • 6.最后FuntureTaskCallable属性的call()方法执行完成后会将结果保存在FutrueTaskoutcome属性中
    • 7.到此异步returnnableThread线程执行完毕,main线程处理完自己的事情后通过futureTaskget()方法获取异步执行结果
  • 3.获取结果的两种情况
    • 1.情况一
      • 1.callable.call()执行完成,futureTask的结果outcome不为空,此时futrueTask.get()会直接取回outcome结果返回给main主线程
    • 2.情况二
      • 1.callable.call()还没执行完,futureTask的结果outcome为空,此时main主线程作为结果获取线程会被阻塞,一直阻塞到callable.call()执行完成
      • 2.当执行完成后,最终结果会保存到outcome中,futrueTask会唤醒main主线程去获取callable.call()执行结果
      • 3.注意:如果callable.call()方法执行完后返回值为null,这种情况并不会阻塞,因为其只和状态有关而和返回的具体值无关

4.线程创建方法四:通过线程池创建线程

  • 1.上述所创建的Thread实例执行完成之后都进行了销毁,这些线程实例都不可复用
  • 2.实际创建一个线程实例时间成本,资源耗费都很高,高并发的场景中不能频繁进行线程实例的创建和销毁,而是需要对以已经创建好的线程实例进行复用,因此需要使用线程池
  • 3.线程池会维护指定数量的活跃可用线程,避免了重复创建造成的资源浪费
  • 4.Java中和线程池相关的主要接口和类
    • 1.Executor接口
    • 2.ExecutorService接口
    • 3.Executors
    • 3.ThreadPoolExecutor
1.Executor接口
package java.util.concurrent;

public interface Executor {

   void execute(Runnable command);
}
  • 1.Exector接口位于JUC包中,其是一个顶层接口并只声明了一个方法execute(Runnable)
  • 2.execute(Runnable)方法
    • 1.接收Runnable对象作为参数,并且以异步的方式执行,该命令可以在一个新线程中,一个池线程中或调用线程中执行,由Executor实现决定
2.ExecutorService接口
package java.util.concurrent;
import java.util.List;
import java.util.Collection;

public interface ExecutorService extends Executor {
	// 启动有序关机,其中执行先前提交的任务,但不接受新任务。如果调用已经关闭,则没有额外的效果
   void shutdown();
   // 尝试停止所有正在执行的任务,停止对等待任务的处理,并返回等待执行的任务列表,此方法不等待正在执行的任务终止,而是使用awaitTermination来完成
   List<Runnable> shutdownNow();
	// 如果执行器已关闭,则返回true
   boolean isShutdown();
   
   boolean isTerminated();

   boolean awaitTermination(long timeout, TimeUnit unit)
       throws InterruptedException;
	// 提交一个返回值的任务以供执行,并返回表示该任务的挂起结果的Future。Future的get方法将在任务成功完成时返回任务的结果
   <T> Future<T> submit(Callable<T> task);

   Future<?> submit(Runnable task);

   <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
       throws InterruptedException;

   <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                 long timeout, TimeUnit unit)
       throws InterruptedException;

   <T> T invokeAny(Collection<? extends Callable<T>> tasks)
       throws InterruptedException, ExecutionException;
       
   <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                   long timeout, TimeUnit unit)
       throws InterruptedException, ExecutionException, TimeoutException;
}
  • 1.ExecutorService 接口位于JUC包中,其继承Executor接口,因此继承了父接口的execute()抽象方法
    在这里插入图片描述
  • 2.submit(Runnable task)方法
    • 1.接收一个Runnable的实现作为参数并返回一个 Future对象
    • 2.该Future对象可用于判断Runnable是否结束执行,因为其get()方法是阻塞执行
    • 3.因为sleep()方法需要抛出受检异常,所以通过@SneakyThrows进行捕获,其中的@SneakyThrows注解可参考https://blog.csdn.net/qq_22162093/article/details/115486647
      在这里插入图片描述
  • 3.submit(Callable< T > task)方法
    • 1.类似submit(Runnable task)方法,区别在于接收不同的参数类型
    • 2.Callablecall()方法可以返回结果且可以抛出异常,Runnablerun() 方法不能返回结果也不能抛出异常
    • 3.Callable的返回值可以从返回的Future对象中获取
      在这里插入图片描述
  • 4.inVokeAny()方法
    • 1.接收一个包含Callable对象的集合作为参数
    • 2.调用该方法不会返回Future对象,而是返回集合中某一个Callable对象的结果
    • 3.无法保证调用之后返回的结果属于哪个Callable,只知道属于该Callable 集合中的一个执行结束的Callable对象
    • 4.如果一个任务运行完毕或抛出异常则会取消其它的Callable的执行
      在这里插入图片描述
      在这里插入图片描述
  • 5.invokeAll()方法
    • 1.接收一个包含Callable对象的集合作为参数
    • 2.调用参数集合中的所有Callable对象,并且返回一个包含Future对象的集合,通过该集合来管理每个Callable的执行结果
    • 3.注意:任务有可能因为异常而导致运行结束,所以可能并不是真的成功运行,但是没有办法通过Future对象来感知到这个差异
  • 6.ExecuteService的关闭
    • 1.使用ExecutorService完毕之后应该关闭它,这样才能保证线程不会继续保持运行状态
    • 2.如果程序通过main()方法启动并且主线程退出了程序,如果还有活动的ExecutorService存在程序中则程序将会继续保持运行状态,存在于 ExecutorService中的活动线程会阻止Java虚拟机关闭
    • 3.为了关闭在ExecutorService中的线程可以调用shutdown()方法,ExecutorService并不会马上关闭所,而是不再接收新的任务,一旦所有的线程结束执行当前任务,ExecutorServie才会真的关闭,所有在调用shutdown()方法之前提交到ExecutorService的任务都会执行
    • 4.如果希望立即关闭ExecutorService可以调用shutdownNow()方法,该方法会尝试马上关闭所有正在执行的任务,并且跳过所有已经提交但是还没有运行的任务,但是对于正在执行的任务,是否能够成功关闭它是无法保证的,有可能真的被关闭掉了也有可能会一直执行到任务结束
3.Executors类
public class Executors {

   public static ExecutorService newFixedThreadPool(int nThreads) {
       return new ThreadPoolExecutor(nThreads, nThreads,
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>());
   }

   public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
       return new ThreadPoolExecutor(nThreads, nThreads,
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>(),
                                     threadFactory);
   }

	public static ExecutorService newWorkStealingPool(int parallelism) {
       return new ForkJoinPool
           (parallelism,
            ForkJoinPool.defaultForkJoinWorkerThreadFactory,
            null, true);
   }

   public static ExecutorService newWorkStealingPool() {
       return new ForkJoinPool
           (Runtime.getRuntime().availableProcessors(),
            ForkJoinPool.defaultForkJoinWorkerThreadFactory,
            null, true);
   }
   
   public static ExecutorService newSingleThreadExecutor() {
       return new FinalizableDelegatedExecutorService
           (new ThreadPoolExecutor(1, 1,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>()));
   }

   public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
       return new FinalizableDelegatedExecutorService
           (new ThreadPoolExecutor(1, 1,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>(),
                                   threadFactory));
   }

   public static ExecutorService newCachedThreadPool() {
       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                     60L, TimeUnit.SECONDS,
                                     new SynchronousQueue<Runnable>());
   }

   public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                     60L, TimeUnit.SECONDS,
                                     new SynchronousQueue<Runnable>(),
                                     threadFactory);
   }

   public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
       return new DelegatedScheduledExecutorService
           (new ScheduledThreadPoolExecutor(1));
   }

   public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
       return new DelegatedScheduledExecutorService
           (new ScheduledThreadPoolExecutor(1, threadFactory));
   }
   
   public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
       return new ScheduledThreadPoolExecutor(corePoolSize);
   }

   public static ScheduledExecutorService newScheduledThreadPool(
           int corePoolSize, ThreadFactory threadFactory) {
       return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
   }

   public static ThreadFactory defaultThreadFactory() {
       return new DefaultThreadFactory();
   }

   public static ThreadFactory privilegedThreadFactory() {
       return new PrivilegedThreadFactory();
   }

   public static <T> Callable<T> callable(Runnable task, T result) {
       if (task == null)
           throw new NullPointerException();
       return new RunnableAdapter<T>(task, result);
   }

   public static Callable<Object> callable(Runnable task) {
       if (task == null)
           throw new NullPointerException();
       return new RunnableAdapter<Object>(task, null);
   }

   /** Cannot instantiate. */
   private Executors() {}
}
  • 1.Java中提供了Executors类,其采用工厂设计模式,相当于一个线程池工厂(注意:是线程池工厂而不是线程工厂),提供了多种不同的线程池可供直接使用,即创建线程池的方法都是静态方法可以直接使用类名调用就能获取
  • 2.newSingleThreadExecutor
    • 1.创建一个单线程的线程池,返回类型为ExecutorService
    • 2.其本质是调用ThreadPoolExecutor类的构造方法并固定线程数为1
      在这里插入图片描述
      在这里插入图片描述
  • 3.newFixedThreadPool
    • 1.创建一个指定数量的线程池,返回类型为ExecutorService
    • 2.其本质是调用ThreadPoolExecutor类的构造方法并指定线程数
    • 3.当线程池中有可用线程,提交的任务就会立即执行,当前线程中没有可用线程,则会将任务放入到一个队列中直到有线程可用
      在这里插入图片描述
      在这里插入图片描述
  • 4.newCachedThreadPool
    • 1.创建一个可缓存的线程池,返回类型为ExecutorService
    • 2.其本质是调用ThreadPoolExecutor类的构造方法
    • 3.该线程池不对线程的数量做限制,只要有线程任务没有线程来处理,就会创建一个线程,同时该线程池有一个回收的功能,如果某个线程超过60秒还没有任务,就会被自动回收掉
      在这里插入图片描述
      在这里插入图片描述
  • 5.newScheduledThreadPool
    • 1.创建一个可用于执行周期性任务的线程池,返回类型为ScheduledExecutorService
    • 2.其本质是调用ScheduledThreadPoolExecutor类的构造方法
      在这里插入图片描述
      在这里插入图片描述
  • 6.newWorkStealingPool
    • 1.创建了一个抢占式的线程池,返回类型为ExecutorService
    • 2.其本质是调用ForkJoinPool类的构造方法
    • 3.ForkJoinPoolJDK7引入的一种新线程池,同ThreadPoolExecutor一样也继承AbstractExecutorService抽象类
    • 4.ForkJoinPool使用一个无限队列来保存需要执行的任务,线程的数量可通过构造函数传入,如果没有传入则当前计算机可用的CPU数量会被设置为线程数量作为默认值
    • 5.ForkJoinPool能够实现工作窃取(Work Stealing),该线程池的每个线程中会维护一个队列来存放需要被执行的任务,当线程自身队列中的任务都执行完毕后,会从别的线程中拿到未被执行的任务并帮助执行
    • 6.newWorkStealingPool会创建一个含有足够多线程的线程池来维持相应的并行级别,通过工作窃取的方式使得多核的CPU不会闲置,总会有活着的线程让CPU去运行
      在这里插入图片描述
      在这里插入图片描述
4.ThreadPoolExecutor类

在这里插入图片描述

public class ThreadPoolExecutor extends AbstractExecutorService {

   private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
   // 线程池线程数的bit数,其中Integer.SIZE = 32,所以COUNT_BITS = 29
   private static final int COUNT_BITS = Integer.SIZE - 3;
   // 线程池中最大线程容量
   private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

   // runState is stored in the high-order bits
   // 接受新任务并处理排队任务
   private static final int RUNNING    = -1 << COUNT_BITS;
   // 不接受新任务,而是处理已排队的任务
   private static final int SHUTDOWN   =  0 << COUNT_BITS;
   // 不接受新任务,不处理排队的任务,不中断正在进行的任务
   private static final int STOP       =  1 << COUNT_BITS;
   // 所有任务都已终止,workerCount为零,转换到TIDYING状态的线程将运行terminated()钩子方法
   private static final int TIDYING    =  2 << COUNT_BITS;
   // 已完成
   private static final int TERMINATED =  3 << COUNT_BITS;

   // Packing and unpacking ctl
   // 获取线程池地运行状态
   private static int runStateOf(int c)     { return c & ~CAPACITY; }
   // 获取有效工作线程地数量
   private static int workerCountOf(int c)  { return c & CAPACITY; }
   // 组装线程数量和线程池状态
   private static int ctlOf(int rs, int wc) { return rs | wc; }

   private static boolean runStateLessThan(int c, int s) {
       return c < s;
   }

   private static boolean runStateAtLeast(int c, int s) {
       return c >= s;
   }

   private static boolean isRunning(int c) {
       return c < SHUTDOWN;
   }

   private boolean compareAndIncrementWorkerCount(int expect) {
       return ctl.compareAndSet(expect, expect + 1);
   }

   private boolean compareAndDecrementWorkerCount(int expect) {
       return ctl.compareAndSet(expect, expect - 1);
   }

   private void decrementWorkerCount() {
       do {} while (! compareAndDecrementWorkerCount(ctl.get()));
   }

   private final BlockingQueue<Runnable> workQueue;

   private final ReentrantLock mainLock = new ReentrantLock();

   private final HashSet<Worker> workers = new HashSet<Worker>();

   private final Condition termination = mainLock.newCondition();

   private int largestPoolSize;

   private volatile ThreadFactory threadFactory;

   private volatile RejectedExecutionHandler handler;

   private volatile long keepAliveTime;

   private volatile boolean allowCoreThreadTimeOut;

   private volatile int corePoolSize;

   private volatile int maximumPoolSize;
   
   private static final RejectedExecutionHandler defaultHandler =
       new AbortPolicy();

   private static final RuntimePermission shutdownPerm =
       new RuntimePermission("modifyThread");

	public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue) {
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            Executors.defaultThreadFactory(), defaultHandler);
   }

   public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue,
                             ThreadFactory threadFactory) {
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            threadFactory, defaultHandler);
   }

   public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue,
                             RejectedExecutionHandler handler) {
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            Executors.defaultThreadFactory(), handler);
   }

   public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue,
                             ThreadFactory threadFactory,
                             RejectedExecutionHandler handler) {
       if (corePoolSize < 0 ||
           maximumPoolSize <= 0 ||
           maximumPoolSize < corePoolSize ||
           keepAliveTime < 0)
           throw new IllegalArgumentException();
       if (workQueue == null || threadFactory == null || handler == null)
           throw new NullPointerException();
       this.corePoolSize = corePoolSize;
       this.maximumPoolSize = maximumPoolSize;
       this.workQueue = workQueue;
       this.keepAliveTime = unit.toNanos(keepAliveTime);
       this.threadFactory = threadFactory;
       this.handler = handler;
   }
	public void execute(Runnable command) {
       if (command == null)
           throw new NullPointerException();
       int c = ctl.get();
       // 1.工作线程数量 < corePoolSize => 直接创建线程执行任务
       if (workerCountOf(c) < corePoolSize) {
           if (addWorker(command, true))
               return;
           c = ctl.get();
       }
       // 工作线程数量 >= corePoolSize && 线程池处于运行状态  => 将任务添加至阻塞队列中workQueue.offer(command)
       if (isRunning(c) && workQueue.offer(command)) {
           int recheck = ctl.get();
       /**
        * 为什么需要double check线程池地状态?
        * 1.往阻塞队列中添加任务地时候,有可能阻塞队列已满,需要等待其他的任务移出队列,在这个过程中,线程池的状态可能会发生变化,所以需要doublecheck
        * 2.如果在往阻塞队列中添加任务地时候,线程池地状态发生变化,则需要将任务remove
        */
           if (! isRunning(recheck) && remove(command))
               reject(command);
           else if (workerCountOf(recheck) == 0)
               addWorker(null, false);
       }
       else if (!addWorker(command, false))
           reject(command);
   }

	private boolean addWorker(Runnable firstTask, boolean core) {
       retry:
       for (;;) {
           int c = ctl.get();
           int rs = runStateOf(c);

           // Check if queue empty only if necessary.
           // 线程池状态处于非RUNNING状态,添加worker失败
           if (rs >= SHUTDOWN &&
               ! (rs == SHUTDOWN &&
                  firstTask == null &&
                  ! workQueue.isEmpty()))
               return false;
			// 判断线程池中线程数量是否处于该线程池允许的最大线程数量,如果允许创建线程,则cas更新线程池中线程数量,并退出循环检查,执行下面创建线程地逻辑
           for (;;) {
               int wc = workerCountOf(c);
               if (wc >= CAPACITY ||
                   wc >= (core ? corePoolSize : maximumPoolSize))
                   return false;
               if (compareAndIncrementWorkerCount(c))
                   break retry;
               c = ctl.get();  // Re-read ctl
               if (runStateOf(c) != rs)
                   continue retry;
               // else CAS failed due to workerCount change; retry inner loop
           }
       }

       boolean workerStarted = false;
       boolean workerAdded = false;
       Worker w = null;
       try {
       		// 创建线程
           w = new Worker(firstTask);
           final Thread t = w.thread;
           if (t != null) {
               final ReentrantLock mainLock = this.mainLock;
               mainLock.lock();
               try {
                   // Recheck while holding lock.
                   // Back out on ThreadFactory failure or if
                   // shut down before lock acquired.
                   int rs = runStateOf(ctl.get());
					// 如果线程池处于RUNNING状态,并且线程已经启动则提前抛出线程异常启动异常
                   if (rs < SHUTDOWN ||
                       (rs == SHUTDOWN && firstTask == null)) {
                       if (t.isAlive()) // precheck that t is startable
                           throw new IllegalThreadStateException();
                        // 将线程加入已创建地线程集合,更新用于追踪线程池中线程数量largestPoolSize字段
                       workers.add(w);
                       int s = workers.size();
                       if (s > largestPoolSize)
                           largestPoolSize = s;
                       workerAdded = true;
                   }
               } finally {
                   mainLock.unlock();
               }
               // 启动线程执行任务
               if (workerAdded) {
               	// 启动线程会调用Worker中地runWorker()来执行任务
                   t.start();
                   workerStarted = true;
               }
           }
       } finally {
           if (! workerStarted)
               addWorkerFailed(w);
       }
       return workerStarted;
   }

	final void reject(Runnable command) {
       handler.rejectedExecution(command, this);
   }
   
	final void runWorker(Worker w) {
		// 获取执行任务线程
       Thread wt = Thread.currentThread();
       // 获取执行任务
       Runnable task = w.firstTask;
       // 将的任务置空
       w.firstTask = null;
       w.unlock(); // allow interrupts
       boolean completedAbruptly = true;
       try {
           while (task != null || (task = getTask()) != null) {
               w.lock();
               // If pool is stopping, ensure thread is interrupted;
               // if not, ensure thread is not interrupted.  This
               // requires a recheck in second case to deal with
               // shutdownNow race while clearing interrupt
               // 双重检查线程池是否正在停止,如果线程池停止,并且当前线程能够中断,则中断线程
               if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                     runStateAtLeast(ctl.get(), STOP))) &&
                   !wt.isInterrupted())
                   wt.interrupt();
               try {
              	 // 前置执行任务钩子函数
                   beforeExecute(wt, task);
                   Throwable thrown = null;
                   try {
                   	// 执行当前任务
                       task.run();
                   } catch (RuntimeException x) {
                       thrown = x; throw x;
                   } catch (Error x) {
                       thrown = x; throw x;
                   } catch (Throwable x) {
                       thrown = x; throw new Error(x);
                   } finally {
                   	// 后置执行任务钩子函数
                       afterExecute(task, thrown);
                   }
               } finally {
                   task = null;
                   w.completedTasks++;
                   w.unlock();
               }
           }
           completedAbruptly = false;
       } finally {
       	  // 回收线程
           processWorkerExit(w, completedAbruptly);
       }
   }
  • 1.ThreadPoolExecutor类是Java提供用于创建线程池的类,ThreadPoolExecutor继承AbstractExecutorService抽象类,且其中封装了一个内部类Worker作为工作线程,每个Worker中都维持着一个Thread
  • 2.《阿里巴巴-Java开发手册》中规定不要使用Executors创建线程池而是通过ThreadPoolExecutor创建,该方式可以明确线程池的运行规则,规避资源耗尽的风险
  • 3.ThreadPoolExecutor中使用AtomicInteger类型的ctl属性描述线程池的运行状态和线程数量,通过ctl的高三位来表示线程池的5种状态,低29位表示线程池中现有的线程数量,使用最少的变量来减少锁竞争,提高并发效率
    在这里插入图片描述
  • 4.ThreadPoolExecutor构造函数中各个参数含义
    • 1.int corePoolSize:线程池的核心线程数,决定是创建新的线程来处理提交的任务还是将其放入缓存队列
      • 1.创建了线程池后,默认情况下线程池中并没有任何线程,而是等待有任才创建线程去执行任务
      • 2.当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列workQueue
    • 2.int maximumPoolSize:线程池的最大线程数,决定是创建非核心线程来处理提交的任务还是执行拒绝策略
      • 1.当线程池中的线程数目达到corePoolSize后,会判断线程数是否达到maximumPoolSize
      • 2.当线程池中的线程数目达到maximumPoolSize后,就会执行拒绝策略,否则会创建非核心线程来执行缓存队列workQueue中的任务
    • 3.long keepAliveTime:线程空闲时存活的时间
      • 1.当任务执行完后,线程池中空闲线程数量增多,当超过corePoolSize时,非核心的线程会在指定时间keepAliveTime被销毁
      • 2.默认情况下只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime则会终止,直到线程池中的线程数不超过corePoolSize
      • 3.allowCoreThreadTimeOut属性表示是否回收核心工作线程,默认false表示不回收核心线程,使用allowCoreThreadTimeOut(true)方法可以设置线程池回收核心线程,如果调用allowCoreThreadTimeOut(boolean)方法,线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0
    • 4.TimeUnit unit:空闲存活时间单位,即参数keepAliveTime的时间单位,其值是TimeUnit枚举类
    • 5.BlockingQueue<Runnable> workQueue:任务队列,用于存放已提交但尚未被执行的任务
      • 1.一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列
      • 2.本质是一个阻塞队列BlockingQueue,用来存储等待执行的任务,线程池的排队策略与workQueue有关
      • 3.阻塞队列有以下几种选择
        • 1.SynchronousQueue:直接提交队列
        • 2.ArrayBlockingQueue:有界任务队列
        • 3.LinkedBlockingQueue:无界任务队列
        • 4.PriorityBlockingQueue:优先级队列
    • 6.ThreadFactory threadFactory:线程工厂,用于创建线程执行任务,一般用默认即可,默认使用Executors.defaultThreadFactory()
    • 7.RejectedExecutionHandler handler:拒绝策略,当线程池中的线程数达到maximumPoolSize时,使用某种策略来拒绝任务提交,本质是ThreadPoolExecutor的内部类并且实现了RejectedExecutionHandler接口
      • 1.ThreadPoolExecutor.AbortPolicy:默认策略,丢弃任务并抛出拒绝执行RejectedExecutionException异常
      • 2.ThreadPoolExecutor.DiscardPolicy:丢弃任务但是不抛出异常
      • 3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面(最老)的任务然后重新尝试执行任务(重复此过程)
      • 4.ThreadPoolExecutor.CallerRunsPolicy:交由调用线程处理该任务
      • 5.自定义拒绝策略:扩展RejectedExecutionHandler接口,自定义拒绝策略
        在这里插入图片描述
        在这里插入图片描述
  • 8.其他可参考https://juejin.cn/post/6987576686472593415https://blog.csdn.net/fighting_yu/article/details/89473175
1.直接提交队列

在这里插入图片描述

  • 1.SynchronousQueue:直接提交队列,也称为同步队列
  • 2.SynchronousQueue:一个特殊的BlockingQueue,其没有容量,每执行一个插入操作就会阻塞,需要在执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作
  • 3.上述结果显示当任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常
  • 4.使用SynchronousQueue队列时提交的任务不会被保存,而是马上提交执行
  • 5.如果用于执行任务的线程数量小于等于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据设置的handler执行拒绝策略
  • 6.因此该方式提交的任务不会被缓存起来而是会被马上执行,这种情况下,需要对程序的并发量有准确的评估,才能设置合适的maximumPoolSize数量,否则容易会执行拒绝策略
2.有界任务队列

在这里插入图片描述

  • 1.ArrayBlockingQueue:有界任务队列
  • 2.上述结果显示当任务队列为ArrayBlockingQueue,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中
  • 3.若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize则执行拒绝策略
  • 4.该方式中线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限
3.无界任务队列

在这里插入图片描述

  • 1.LinkedBlockingQueue:无界任务队列
  • 2.上述结果显示当任务队列为LinkedBlockingQueue,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是corePoolSize
  • 3.该方式中maximumPoolSize参数是无效的,哪怕任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后就不会再增加
  • 4.若后续有新的任务加入,则直接进入阻塞队列等待,当使用这种任务队列模式时,一定要注意任务提交与处理之间的协调控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题
4.优先任务队列

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1.PriorityBlockingQueue:优先任务队列
  • 2.上述结果显示当任务队列为PriorityBlockingQueue,除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行且线程池的线程数一直为corePoolSize
  • 3.PriorityBlockingQueue其实是一个特殊的无界队列,其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量
  • 4.只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行
5.线程池的生命周期

在这里插入图片描述
在这里插入图片描述

6.线程池的优缺点
  • 1.优点
    • 1.降低资源消耗:复用已创建的线程来降低创建和销毁线程的消耗
    • 2.提高响应速度:任务到达时可以不需要等待线程的创建立即执行
    • 3.提高线程的可管理性:使用线程池能够统一的分配、调优和监控
  • 2.缺点
    • 1.多线程会占CPU,使用多线程的地方并发量比较高时会导致其他功能响应很慢
7.线程池的执行流程

在这里插入图片描述
在这里插入图片描述

  • 1.一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是Runnable类型对象的run()方法
  • 2.当一个任务通过execute(Runnable)方法欲添加到线程池时
  • 3.如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务
  • 4.如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列
  • 5.如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满了并且线程池中的数量小于maximumPoolSize,创建新的线程来处理被添加的任务
  • 6.如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满了并且线程池中的数量等于maximumPoolSize,则通过 handler所指定的拒绝策略来处理此任务
  • 7.处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务
  • 8.当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime线程将被终止,这样线程池可以动态的调整池中的线程数

3.进程与线程的区别

  • 1.线程是进程代码段的一次顺序执行流程,一个进程由一个或多个线程组成,一个进程至少有一个线程
  • 2.线程是CPU调度的最小单位,进程是操作系统分配资源的最小单位
  • 3.线程是基于高并发的调度诉求从进程内部演进而来的,线程的出现既充分发挥了CPU的计算性能,又弥补了进程调度过于笨重的问题
  • 4.进程之间是相互独立的,但进程内部的各个线程之间并不完全独立,各个线程之间共享进程的方法区内存,堆内存,系统资源(文件句柄,系统信号等)
  • 5.切换速度不同:线程上下文切换比进程上下文切换要快

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2198006.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

常见的负载均衡

1.常见的负载均衡服务 负载均衡服务是分布式系统中用于分配网络流量和请求的关键组件&#xff0c;它可以帮助提高应用程序的可用性、可扩展性和响应速度。以下是一些常用的负载均衡服务&#xff1a; Nginx&#xff1a;一个高性能的Web服务器和反向代理&#xff0c;广泛用于实现…

【基于YOLOv5的反光衣检测预警系统】可检测图片、视频、摄像头,支持GPU加速检测以及语音播报预警

文章目录 演示视频系统界面项目环境系统界面功能项目框架介绍项目代码获取 ➷点击跳转至文末完整项目代码下载处☇ 演示视频 【基于YOLOv5的反光衣检测预警系统】可检测图片、视频、摄像头&#xff0c;支持GPU加速检测以及语音播报预警 系统界面 项目环境 项目运行所需环境为…

机器学习可视化教程——混淆矩阵与回归图

机器学习可视化教程——混淆矩阵与回归图 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0c;目标检测&#xff0c;图像分类&#xff0c;姿态识别&#xff0c;语…

大模型从入门到应用——LangChain:模型(Models)-[大型语言模型(LLMs):基础知识!

LangChain系列文章&#xff1a; 基础知识快速入门 安装与环境配置链&#xff08;Chains&#xff09;、代理&#xff08;Agent:&#xff09;和记忆&#xff08;Memory&#xff09;快速开发聊天模型 模型&#xff08;Models&#xff09; 基础知识大型语言模型&#xff08;LLMs&a…

MATLAB APPdesigner中的日期选择器怎样实时显示时间

文章目录 1.问题描述2.代码设置代码示例解释 1.问题描述 我们在做MATLAB的时候&#xff0c;一般需要在APP界面中加上时间显示&#xff0c;像下图中的右上角&#xff0c;在组件中有日期选择器&#xff0c;但是这个并不是实时显示的&#xff0c;我们还需要自己进行设置。 2.代码…

python pyinstaller打包exe遇到报错:RuntimeError: input(): lost sys.stdin

在使用python中的pyinstaller命令打包exe遇到报错:RuntimeError: input(): lost sys.stdin 一、问题复现 import datetimedef record_log():project_name = input("请输入项目名称:")l

毕设分享 基于python的搜索引擎设计与实现

文章目录 0 简介1 课题简介2 系统设计实现2.1 总体设计2.2 搜索关键流程2.3 推荐算法2.4 数据流的实现 3 实现细节3.1 系统架构3.2 爬取大量网页数据3.3 中文分词3.4 相关度排序第1个排名算法&#xff1a;根据单词位置进行评分的函数第2个排名算法&#xff1a;根据单词频度进行…

硬件开发笔记(三十一):TPS54331电源设计(四):PCB布板12V转5V电路、12V转3.0V和12V转4V电路

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/142757509 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

C#/.NET/.NET Core技术前沿周刊 | 第 8 期(2024年10.01-10.06)

前言 C#/.NET/.NET Core技术前沿周刊&#xff0c;你的每周技术指南针&#xff01;记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿&#xff0c;助力技术成长与视野拓宽。 欢迎投稿&…

Hadoop的三种运行模式:单机模式、伪分布式模式和完全分布式模式

单机模式 单机模式是Hadoop最简单的运行模式。在单机模式下&#xff0c;所有Hadoop组件都运行在单个机器上&#xff0c;包括HDFS、MapReduce等。由于只有一个节点参与计算&#xff0c;单机模式适用于开发和测试阶段&#xff0c;不适合用于处理大规模数据。在单机模式下&#xf…

IDE启动失败

报错&#xff1a;Cannot connect to already running IDE instance. Exception: Process 24,264 is still running 翻译&#xff1a;无法连接到已运行的IDE实例。异常:进程24,264仍在运行 打开任务管理器&#xff0c;找到PID为24264的CPU线程&#xff0c;强行结束即可。 【Ct…

国内知名人工智能AI大模型专家培训讲师唐兴通讲授AI办公应用人工智能在营销与销售过程中如何应用数字化赋能

AI如火如荼&#xff0c;对商业与社会影响很大。 目前企业广泛应用主要是在营销、销售方向&#xff0c;提升办公效率等方向。 从喧嚣的AI导入营销与销售初步阶段&#xff0c;那么当下&#xff0c;领先的组织与个人现在正在做什么呢&#xff1f; 如何让人性注入冷冰冰的AI&…

YOLO11训练自己的数据集(吸烟、跌倒行为检测)

YOLO11训练自己的数据集&#xff08;吸烟、跌倒行为检测&#xff09; 前言相关介绍前提条件实验环境安装环境项目地址LinuxWindows 使用YOLO11训练自己的数据集进行吸烟、跌倒行为检测准备数据进行训练进行预测进行验证 参考文献 前言 由于本人水平有限&#xff0c;难免出现错漏…

可以自动点击网页按钮的 Chrome 插件(manifest_v3 20241008)

这是我针对一个 vue 单页应用做的自动点击插件。他可以在这个 vue 单页应用的某一个子页面加载时&#xff0c;自动点击页面上的按钮。 分享那个这个案例的意义在于&#xff0c;vue 单页应用不同于一般的网页&#xff0c;他有很多事件是不触发的&#xff0c;需要自己想办法处理…

下一个赛场见!从中网看海尔智家的全球化布局

下一个赛场见&#xff01; 我想这是每个看完中国网球公开赛女子决赛后&#xff0c;清晰又坚定的约定。正如国庆假期后&#xff0c;下一个假期见成为很多人的期待。 10月6日&#xff0c;备受瞩目的中国网球公开赛女子决赛迎来了最高光时刻并落下帷幕。 美国选手高芙获得冠军&…

aws(学习笔记第四课) AWS的IAM服务,用于授权的策略,用户和组以及角色

aws(学习笔记第四课) AWS的IAM服务&#xff0c;用于授权的策略&#xff0c;用户和组以及角色 学习内容&#xff1a; AWS的IAM服务用于AWS授权的策略用于认证AWS的用户和组用于认证AWS的角色 1. AWS的IAM服务 IAM用户&#xff0c;角色的区别 IAM用户服务 Identity and Access…

5.k8s:helm包管理器,prometheus监控,elk,k8s可视化

目录 一、Helm 包管理器 1.什么是 Helm 2.安装Helm &#xff08;3&#xff09;Helm常用命令 &#xff08;4&#xff09;目录结构 &#xff08;5&#xff09;使用Helm完成redis主从搭建 二、Prometheus集群监控 1.监控方案 2.Prometheus监控k8s 三、ELK日志搜集 1.el…

快速学习开源 Docker 镜像仓库 Harbor

Harbor 是由 VMware 公司开源的企业级容器镜像仓库&#xff0c;用于存储、管理和分发 Docker 容器镜像。它构建在开源的 Docker Registry 之上&#xff0c;通过添加一系列企业级功能&#xff0c;如安全、身份验证、访问控制和审计等&#xff0c;满足企业在容器化应用部署中的复…

gaussdb hccdp理论考试总结

判断题1分&#xff0c;单选题2分&#xff0c;多选题3分 共50道题&#xff0c;满分100分&#xff0c;60分通过。 理论考试知识点占比&#xff1a; 理论考试参考策略&#xff1a; 1.7张PPT看一遍 2.思考题做一遍 3.模拟题做一遍 4.7张PPT再看一遍 5.考题知识点过一遍 6.考试前一…

【LeetCode: 1436. 旅行终点站 | 哈希表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…