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中的虚拟机栈和本地方法栈
)- 2
JDK8
中每个线程在创建时默认被分配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.上述代码定义了三个方法
main
,anotherFun
,anotherFun2
,并且这三个方法有相同的三个局部变量a
,b
,c
- 3.上述代码中
JVM
的执行流程
- 1.当执行
main()
方法时,JVM
为main()
方法分配一个栈帧,保存三个局部变量,然后将栈帧压入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_WAITING
(LockSupport.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.线程状态:
runnable
,waiting on condition
等等- 6.线程名称:
Service Thread
,main
等等- 7.
GC tack thread
:垃圾回收线程,该类线程会负责进行垃圾回收,通常JVM
会启动多个GC
线程,GC线程的名称中,#
后面的数字会累加- 8.
VM Periodic Task Thread
:JVM
周期性任务调度的线程,该线程在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.假设有两个线程
A
和B
,线程A
在执行过程中对另一个线程B
的执行有依赖- 2.具体的依赖为:线程
A
需要将线程B
的执行流程合并到自己的执行流程中,这就是线程合并,被动方线程B
叫做被合并线程- 3.
join()
方法是Thread
类的一个实例方法,有三个重载版本,需要使用被合并线程
的句柄(指针,引用)去调用- 4.执行
thread.join()
代码的当前线程为合并线程
,thread
为被合并线程
,合并线程进入WAITING
状态让出CPU
,被合并线程获得CPU
进入RUNNABLE
状态- 5.如果设置了
被合并线程
的执行时间millis
或millis+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
对象锁的线程,进入相应的代码临界区
后,调用相应的object
的wait()
方法去等待其对象锁(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
线程设置成守护线程,守护线程daemonThread
的run()
方法中设置了死循环,启动后理论上永远也不会停止- 4.结果显示
- 1.
main
线程也是一条用户线程,main
线程在创建和启动daemonThread
和userThread
后提前结束- 2.虽然
main
线程结束,但是其他2
个线程还在继续执行,其中userThread
是用户线程,所以进程还不能结束- 3.当用户线程
userThread
的run()
方法执行完成后,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.注意:
java
中Thread
类线程执行完run()
方法后,一定会自动执行notifyAll()
方法
1.线程间的通信
- 1.
线程通信
:指当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺- 2.线程通信的方式
- 1.共享内存
- 1.
volatile
共享内存- 2.具体可参考
Java SE
的volatile
- 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
实例,然后直接通过实现Runnable
的run()
方法达到线程并发执行的目的- 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.为了解决异步执行的结果问题,
Java
在1.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
接口继承了Runnable
和Future
两个接口从而实现两个目标
- 1.可以作为
Thread
线程实例的target
:RunnableFuture
继承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
实例作为target
的Thread
线程- 2.上述代码执行流程
- 1.首先执行
main
主线程- 2.然后
main
主线程通过thread.start()
方法启动returnableThread
线程- 3.
main
主线程继续执行,ruturnableThread
线程开始并发执行- 4.
returnableThread
线程首先执行thread.run()
方法,然后会执行到其target
(futureTask
实例)的run()
方法- 5.接着
futureTask.run()
方法中执行futureTask
的callable
属性的call()
方法- 6.最后
FuntureTask
的Callable
属性的call()
方法执行完成后会将结果保存在FutrueTask
的outcome
属性中- 7.到此异步
returnnableThread
线程执行完毕,main
线程处理完自己的事情后通过futureTask
的get()
方法获取异步执行结果- 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.
Callable
的call()
方法可以返回结果且可以抛出异常,Runnable
的run()
方法不能返回结果也不能抛出异常- 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.
ForkJoinPool
是JDK7
引入的一种新线程池,同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/6987576686472593415
和https://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.切换速度不同:线程上下文切换比进程上下文切换要快