进程
线程
线程方法
线程原理
线程状态
查看线程
进程
概述
进程:程序是静止的,进程实体的运行过程就是进程,是系统进行资源分配的基本单位. 一般来说,一个进程由程序段(包含代码、指令集合)、数据段(进程的操作数据)和进程控制块(进程描述信息、控制信息,进程的唯一标志)三部分组成。
线程:线程是属于进程的,是一个基本的 CPU 执行单元,是程序执行流的最小单元。线程是进程中的一个实体,是系统独立调度的基本单位,线程本身不拥有系统资源,只拥有一点在运行中必不可少的资源,与同属一个进程的其他线程共享进程所拥有的全部资源。一个标准的线程主要由三部分组成,即线程描述信息、程序计数器(Program Counter, PC)和栈内存。栈内存就是虚拟机内存或者叫做方法栈,主要存放栈帧(方法帧:方法的数据),栈内存是代码段中局部变量的存储空间,为线程所独立拥有,在线程之间不共享。参考JVM篇笔记。程序计数器很重要,它记录着线程下一条指令的代码段内存地址。每个线程在创建时默认被分配1MB大小的栈内存。栈内存和堆内存不同,栈内存不受垃圾回收器管理。
区别总结:
- 线程是CPU调度的最小单位,进程是操作系统分配资源的最小单位。线程的划分尺度小于
进程,使得多线程程序的并发性高。 - 进程之间是相互独立的,但进程内部各个线程之间并不完全独立。各个线程之间共享进程
的方法区内存、堆内存、系统资源。 - 切换速度不同,线程上下文切换比进程上下文切换要快得多。所以,有时线程也称为轻量
级进程。
关系:一个进程可以包含多个线程,这就是多线程,使多道程序更好的并发执行,提高资源利用率和系统吞吐量,增强操作系统的并发性能。
并发并行:
-
并行(parallel):在同一时刻,有多个指令在多个 CPU 上同时执行。CPU有多个核心,每个核心可以同时并行执行任务。
-
并发(concurrent):在同一时刻,有多个指令在单个 CPU 上交替执行,由操作系统任务调度器分配CPU时间片。单核CPU操作系统微观上实际上串行执行。
正真在多核操作系统中,既有并行也有并发,因为进程数(线程数量)大于核心数。
同步异步:
-
需要等待结果返回(阻塞),才能继续运行就是同步。
-
不需要等待结果返回,就能继续运行就是异步,多线程实现。
redis中keys命令是同步的,scans命令是异步的。UI程序中其他操作需要开启子线程完成,避免UI阻塞。读取某个大文件,开启子线程完成,避免阻塞主线程。
IO控制器则专门负责控制IO设备的操作,与IO设备进行通信,并管理数据的传输。数据交换:CPU和IO控制器都涉及数据的交换。
线程
创建线程
Thread
Thread 创建线程方式:创建线程类,匿名内部类方式
- start() 方法底层其实是给 CPU 注册当前线程,并且触发 run() 方法执行
- 线程的启动必须调用 start() 方法,如果线程直接调用 run() 方法,相当于变成了普通类的执行,此时主线程将只有执行该线程。
- 建议线程先创建子线程,主线程的任务放在之后,否则主线程(main)永远是先执行完。
- 可以通过get set 获取|设置 Name、Priority、State 等属性。
Thread 构造器:
public Thread()
public Thread(String name)
public class ThreadDemo {public static void main(String[] args) {Thread t = new MyThread();t.start();for(int i = 0 ; i < 100 ; i++ ){System.out.println("main线程" + i)}// main线程输出放在上面 就变成有先后顺序了,因为是 main 线程驱动的子线程运行}
}
class MyThread extends Thread {@Overridepublic void run() {for(int i = 0 ; i < 100 ; i++ ) {System.out.println("子线程输出:"+i)}}
}
继承 Thread 类的优缺点:
- 优点:编码简单
- 缺点:线程类已经继承了 Thread 类无法继承其他类了,功能不能通过继承拓展(单继承的局限性)
Runnable
Runnable 创建线程方式:创建线程类,匿名内部类方式
Thread 的构造器:
public Thread(Runnable target)
public Thread(Runnable target, String name)
public class ThreadDemo {public static void main(String[] args) {Runnable target = new MyRunnable();Thread t1 = new Thread(target,"1号线程");t1.start();Thread t2 = new Thread(target);//Thread-0}
}public class MyRunnable implements Runnable{@Overridepublic void run() {for(int i = 0 ; i < 10 ; i++ ){System.out.println(Thread.currentThread().getName() + "->" + i);}}
}
Thread 类本身也是实现了 Runnable 接口,Thread 类中持有 Runnable 的属性,执行线程 run 方法底层是调用 Runnable的run:
public class Thread implements Runnable {private Runnable target;public void run() {if (target != null) {// 底层调用的是 Runnable 的 run 方法target.run();}}
}
Runnable 方式的优缺点:
-
缺点:代码复杂一点。
-
优点:
-
线程任务类只是实现了 Runnable 接口,可以继续继承其他类,避免了单继承的局限性
-
同一个线程任务对象可以被包装成多个线程对象
-
适合多个多个线程去共享同一个资源
-
实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立
-
线程池可以放入实现 Runnable 或 Callable 线程任务对象,组合优于继承。
-
通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。
-
Callable
实现 Callable 接口:
1)创建一个Callable接口的实现类,并实现其call()方法,编写好异步执行的具体逻辑,可以有返回值。
2)使用Callable实现类的实例构造一个FutureTask实例。
3)使用FutureTask实例作为Thread构造器的target入参,构造新的Thread线程实例。
4)调用Thread实例的start()方法启动新线程,启动新线程的run()方法并发执行。其内部的执行过程为:启动Thread实例的run()方法并发执行后,会执行FutureTask实例的run()方法,最终会并发执Callable实现类的call()方法
public FutureTask(Callable<V> callable)
:未来任务对象,在线程执行完后得到线程的执行结果
- FutureTask 就是 Runnable 对象,因为 Thread 类只能执行 Runnable 实例的任务对象,所以把 Callable 包装成未来任务对象
- 线程池部分详解了 FutureTask 的源码
public V get()
:同步等待 task 执行完毕的结果,如果在线程中获取另一个线程执行结果,会阻塞等待,用于线程同步。如果outcome为空则阻塞,不为空则直接返回结果。
- get() 线程会阻塞等待任务执行完成
- run() 执行完后会把结果设置到 FutureTask 的一个成员变量,get() 线程可以获取到该变量的值
优缺点:
- 优点:同 Runnable,并且能得到线程执行的结果。逻辑和数据更好分离。有返回值。call()抽象方法还有一个Exception的异常声明,容许方法的实现版本的内部异常直接抛出,并且可以不予捕获。
- 缺点:编码复杂
public class ThreadDemo {public static void main(String[] args) {Callable call = new MyCallable();FutureTask<String> task = new FutureTask<>(call);Thread t = new Thread(task);t.start();try {String s = task.get(); // 获取call方法返回的结果(正常/异常结果)System.out.println(s);} catch (Exception e) {e.printStackTrace();}}public class MyCallable implements Callable<String> {@Override//重写线程任务类方法public String call() throws Exception {return Thread.currentThread().getName() + "->" + "Hello World";}
}
Linux 命令查看java进程:ps -ef|grep java 或者 jps
线程方法
API
Thread 类 API:
方法 | 说明 |
---|---|
public void start() | 启动一个新线程,Java虚拟机调用此线程的 run 方法 |
public void run() | 线程启动后调用该方法 |
public void setName(String name) | 给当前线程取名字 |
public void getName() | 获取当前线程的名字 线程存在默认名称:子线程是 Thread-索引,主线程是 main |
public static Thread currentThread() | 获取当前线程对象,代码在哪个线程中执行 |
public static void sleep(long time) | 让当前线程休眠多少毫秒再继续执行 Thread.sleep(0) : 让操作系统立刻重新进行一次 CPU 竞争 |
public static native void yield() | 提示线程调度器让出当前线程对 CPU 的使用 |
public final int getPriority() | 返回此线程的优先级 |
public final void setPriority(int priority) | 更改此线程的优先级,常用 1 5 10 |
public void interrupt() | 中断这个线程,异常处理机制 |
public static boolean interrupted() | 判断当前线程是否被打断,清除打断标记 |
public boolean isInterrupted() | 判断当前线程是否被打断,不清除打断标记 |
public final void join() | 等待这个线程结束 |
public final void join(long millis) | 等待这个线程死亡 millis 毫秒,0 意味着永远等待 |
public final native boolean isAlive() | 线程是否存活(还没有运行完毕) |
public final void setDaemon(boolean on) | 将此线程标记为守护线程或用户线程 |
run start
run:称为线程体,包含了要执行的这个线程的内容,方法运行结束,此线程随即终止。直接调用 run 是在主线程中执行了 run,没有启动新的线程,需要顺序执行
start:使用 start 是启动新的线程,此线程处于就绪(可运行)状态,通过新的线程间接执行 run 中的代码
说明:线程控制资源类
run() 方法中的异常不能抛出,只能 try/catch
- 因为父类中没有抛出任何异常,子类不能比父类抛出更多的异常
- 异常不能跨线程传播回 main() 中,因此必须在本地进行处理
sleep yield
sleep:
- 调用 sleep 会让当前线程从
Running
进入Timed Waiting
状态(阻塞) - sleep() 方法的过程中,线程不会释放对象锁
- 其它线程可以使用 interrupt 方法打断|叫醒正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException,可以捕获异常,子线程重新执行!
- 睡眠结束后的线程未必会立刻得到执行,需要抢占 CPU
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield:
- 调用 yield 会让提示线程调度器让出当前线程对 CPU 的使用
- 具体的实现依赖于操作系统的任务调度器
- 会放弃 CPU 资源,锁资源不会释放
join
public final void join():等待这个线程结束
原理:调用者轮询检查线程 alive 状态,t1.join() 等价于:
public final synchronized void join(long millis) throws InterruptedException {// 调用者线程进入 thread 的 waitSet 等待, 直到当前线程运行结束while (isAlive()) {wait(0);}
}
-
join 方法是被 synchronized 修饰的,本质上是一个对象锁,其内部的 wait 方法调用也是释放锁的,但是释放的是当前的线程对象锁,而不是外面的锁
-
当调用某个线程(t1)的 join 方法后,该线程(t1)抢占到 CPU 资源,就不再释放,直到线程执行完毕
线程同步:
- join 实现 线程同步,因为会阻塞等待另一个线程的结束,才能继续向下运行
- 需要外部共享变量,不符合面向对象封装的思想
- 必须等待线程结束,不能配合线程池使用
- Future 实现(同步):get() 方法阻塞等待执行结果
- main 线程接收结果
- get 方法是让调用线程同步等待
public class Test {static int r = 0;public static void main(String[] args) throws InterruptedException {test1();}private static void test1() throws InterruptedException {Thread t1 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}r = 10;});t1.start();t1.join();//不等待线程执行结束,输出的10System.out.println(r);}
}
interrupt
Thread.interrupt()方法并不像Thread.stop()方法那样中止一个正在运行的线程,其作用是设置线
程的中断状态位(为true),至于线程是死亡、等待新的任务还是继续运行至下一步,就取决于这
个程序本身。线程可以不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否
为true)。总之, Thread.interrupt()方法只是改变中断状态,不会中断一个正在运行的线程,线程是否
停止执行,需要用户程序去监视线程的isInterrupted()状态,并进行相应的处理
打断线程
public void interrupt()
:打断这个线程,异常处理机制
public static boolean interrupted()
:判断当前线程是否被打断,打断返回 true,然后会清除打断标记,连续调用两次一定返回 false。实际上interrupt是用来打断运行的线程的,但是打断阻塞的线程会抛出打断异常,我们可以在异常中选择调用interrupt继续打断来达到打断阻塞线程的目的。因此,通过这样处理就能打断运行时线程,也能打断阻塞线程。
public boolean isInterrupted()
:判断当前线程是否被打断,不清除打断标记
打断的线程会发生上下文切换,操作系统会保存线程信息,抢占到 CPU 后会从中断的地方接着运行(打断不是停止)
-
sleep、wait、join 方法都会让线程进入阻塞状态,打断阻塞线程会清空打断状态(false)
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}, "t1");t1.start();Thread.sleep(500);t1.interrupt();System.out.println(" 打断状态: {}" + t1.isInterrupted());// 打断状态: {}false }
-
打断正常运行的线程:不会清空打断状态(true)
public static void main(String[] args) throws Exception {Thread t2 = new Thread(()->{while(true) {Thread current = Thread.currentThread();boolean interrupted = current.isInterrupted();if(interrupted) {System.out.println(" 打断状态: {}" + interrupted);//打断状态: {}truebreak;}}}, "t2");t2.start();Thread.sleep(500);t2.interrupt(); }
打断 park
park 作用类似 sleep,打断 park 线程,不会清空打断状态(true)
public static void main(String[] args) throws Exception {Thread t1 = new Thread(() -> {System.out.println("park...");LockSupport.park();System.out.println("unpark...");System.out.println("打断状态:" + Thread.currentThread().isInterrupted());//打断状态:true}, "t1");t1.start();Thread.sleep(2000);t1.interrupt();
}
如果打断标记已经是 true, 则 park 会失效,无法阻塞线程。
LockSupport.park();// 此时打断标记为true
System.out.println("unpark...");
LockSupport.park();//打断标记为true,阻塞失效,不会阻塞
System.out.println("unpark...");//和上一个unpark同时执行
可以修改获取打断状态方法,使用 Thread.interrupted()
,清除打断标记设置为false
LockSupport 类在 同步 → park-un 详解
终止模式
终止模式之两阶段终止模式:Two Phase Termination
目标:在一个线程 T1 中如何优雅终止线程 T2?优雅指的是给 T2 一个后置处理器。两阶段指的是 正在执行的线程被打断 或者 阻塞的线程被打断两种终止模式,如果是正在执行线程被打断则打断标记为true,可以通过在需要终止的线程内部判断这个标记来选择终止并料理后事;如果是阻塞的状态被打断则会清除标记因此打断标记依然为false并且抛出打断异常,我们可以在异常thread.interrupt();重新设置清除标记为true,让他下轮进行终止。
错误思想:
- 使用线程对象的 stop() 方法停止线程:stop 方法会真正杀死线程,如果这时线程锁住了共享资源,当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
- 使用 System.exit(int) 方法停止线程:目的仅是停止一个线程,但这种做法会让整个程序都停止
两阶段终止模式图示:
打断线程可能在任何时间,所以需要考虑在任何时刻被打断的处理方法:
public class Test {public static void main(String[] args) throws InterruptedException {TwoPhaseTermination tpt = new TwoPhaseTermination();tpt.start();Thread.sleep(3500);tpt.stop();}
}
class TwoPhaseTermination {private Thread monitor;// 启动监控线程public void start() {monitor = new Thread(new Runnable() {@Overridepublic void run() {while (true) {Thread thread = Thread.currentThread();if (thread.isInterrupted()) {System.out.println("后置处理");break;}try {Thread.sleep(1000); // 睡眠System.out.println("执行监控记录"); // 在此被打断不会异常} catch (InterruptedException e) { // 在睡眠期间被打断,进入异常处理的逻辑e.printStackTrace();// 重新设置打断标记,打断 sleep 会清除打断状态thread.interrupt();}}}});monitor.start();}// 停止监控线程public void stop() {monitor.interrupt();}
}
daemon
public final void setDaemon(boolean on)
:如果是 true ,将此线程标记为守护线程
线程启动前调用此方法:
Thread t = new Thread() {@Overridepublic void run() {System.out.println("running");}
};
// 设置该线程为守护线程
t.setDaemon(true);
t.start();
用户线程:平常创建的普通线程
守护线程:服务于用户线程,只要其它非守护线程运行结束了,即使守护线程代码没有执行完,也会强制结束。守护进程是脱离于终端并且在后台运行的进程,脱离终端是为了避免在执行的过程中的信息在终端上显示
说明:当运行的线程都是守护线程,Java 虚拟机将退出,因为普通线程执行完后,JVM 是守护线程,不会继续运行下去
常见的守护线程:
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
不推荐
不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁:
-
public final void stop()
:停止线程运行废弃原因:方法粗暴,除非可能执行 finally 代码块以及释放 synchronized 外,线程将直接被终止,如果线程持有 JUC 的互斥锁可能导致锁来不及释放,造成其他线程永远等待的局面
-
public final void suspend()
:挂起(暂停)线程运行废弃原因:如果目标线程在暂停时对系统资源持有锁,则在目标线程恢复之前没有线程可以访问该资源,如果恢复目标线程的线程在调用 resume 之前会尝试访问此共享资源,则会导致死锁
-
public final void resume()
:恢复线程运行
线程原理
运行机制
Java Virtual Machine Stacks(Java 虚拟机栈):每个线程启动后,虚拟机就会为其分配一块栈内存
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
线程上下文切换(Thread Context Switch):一些原因导致 CPU 不再执行当前线程,转而执行另一个线程
- 线程的 CPU 时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了 sleep、yield、wait、join、park 等方法
程序计数器(Program Counter Register):记住下一条 JVM 指令的执行地址,是线程私有的,存在方法栈中 。
当 Context Switch 发生时,需要由操作系统保存当前线程的状态(PCB 中),并恢复另一个线程的状态,状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等。
线程的调度是在内核态运行的,而线程中的代码是在用户态运行,所以线程切换(状态改变)会导致用户与内核态转换进行系统调用,这是非常消耗性能
Java 中 main 方法启动的是一个进程也是一个主线程,main 方法里面的其他线程均为子线程,main 线程是这些线程的父线程
线程调度
线程调度指系统为线程分配处理器使用权的过程,方式有两种:协同式线程调度、抢占式线程调度(Java 选择)
协同式线程调度:线程的执行时间由线程本身控制
- 优点:线程做完任务才通知系统切换到其他线程,相当于所有线程串行执行,不会出现线程同步问题
- 缺点:线程执行时间不可控,如果代码编写出现问题,可能导致程序一直阻塞,引起系统的奔溃
抢占式线程调度:线程的执行时间由系统分配
- 优点:线程执行时间可控,不会因为一个线程的问题而导致整体系统不可用
- 缺点:无法主动为某个线程多分配时间
Java 提供了线程优先级的机制,优先级会提示(hint)调度器优先调度该线程,但这仅仅是一个提示,调度器可以忽略它。在线程的就绪状态时,如果 CPU 比较忙,那么优先级高的线程会获得更多的时间片,但 CPU 闲时,优先级几乎没作用
说明:并不能通过优先级来判断线程执行的先后顺序
未来优化
内核级线程调度的成本较大,所以引入了更轻量级的协程。用户线程的调度由用户自己实现(多对一的线程模型,多个用户线程映射到一个内核级线程),被设计为协同式调度,所以叫协程
- 有栈协程:协程会完整的做调用栈的保护、恢复工作,所以叫有栈协程
- 无栈协程:本质上是一种有限状态机,状态保存在闭包里,比有栈协程更轻量,但是功能有限
有栈协程中有一种特例叫纤程,在新并发模型中,一段纤程的代码被分为两部分,执行过程和调度器:
- 执行过程:用于维护执行现场,保护、恢复上下文状态
- 调度器:负责编排所有要执行的代码顺序
线程状态
进程的状态参考操作系统:创建态、就绪态、运行态、阻塞态、终止态。
就绪状态和运行状态都是操作系统中的线程状态。在Java语言中,并没有细分这两种状态,而是将这两种状态合并成同一种状态——RUNNABLE状态。因此,在Thread.State枚举类中,没有定义线程的就绪状态和运行状态,只是定义了RUNNABLE状态。这就是Java线程状态和操作系统中的线程状态有所不同的地方。总之, NEW状态的Thread实例调用了start()方法后,线程的状态将变成RUNNABLE状态。尽管如此,线程的run()方法不一定会马上被并发执行,需要在线程获取了CPU时间片之后,才会真正启动并发执行。
线程由生到死的完整过程(生命周期):当线程被创建并启动以后,既不是一启动就进入了执行状态,也不是一直处于执行状态,在 API 中 java.lang.Thread.State
这个枚举中给出了六种线程状态:
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动,还没调用 start 方法,只有线程对象,没有线程特征 |
Runnable(可运行) | 线程可以在 Java 虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器,调用了 t.start() 方法:就绪(经典叫法) |
Blocked(阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入 Blocked 状态;当该线程持有锁时,该线程将变成 Runnable 状态 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,wait、join,该线程进入 Waiting 状态,进入这个状态后不能自动唤醒,必须等待另一个线程调用 notify 或者 notifyAll 方法才能唤醒 |
Timed Waiting (限期等待) | 有几个方法有超时参数,调用将进入 Timed Waiting 状态,这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有 Thread.sleep 、Object.wait、join |
Teminated(结束) | run 方法正常退出而死亡,或者因为没有捕获的异常终止了 run 方法而死亡 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qBpAboPG-1684721263703)(https://seazean.oss-cn-beijing.aliyuncs.com/img/Java/JUC-线程6种状态.png)]
-
NEW → RUNNABLE:当调用 t.start() 方法时,由 NEW → RUNNABLE
-
RUNNABLE <–> WAITING:
-
调用 obj.wait() 方法时
调用 obj.notify()、obj.notifyAll()、t.interrupt():
- 竞争锁成功,t 线程从 WAITING → RUNNABLE
- 竞争锁失败,t 线程从 WAITING → BLOCKED
-
当前线程调用 t.join() 方法,注意是当前线程在 t 线程对象的监视器上等待
-
当前线程调用 LockSupport.park() 方法
-
-
RUNNABLE <–> TIMED_WAITING:调用 obj.wait(long n) 方法、当前线程调用 t.join(long n) 方法、当前线程调用 Thread.sleep(long n)
-
RUNNABLE <–> BLOCKED:t 线程用 synchronized(obj) 获取了对象锁时竞争失败
查看线程
Windows:
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist 查看进程
- taskkill 杀死进程
Linux:
- ps -ef 查看所有进程
- ps -fT -p 查看某个进程(PID)的所有线程
- kill 杀死进程
- top 按大写 H 切换是否显示线程
- top -H -p 查看某个进程(PID)的所有线程
Java:
- jps 命令查看所有 Java 进程
- jstack 查看某个 Java 进程(PID)的所有线程状态
- jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
线程池原理
Java线程的创建非常昂贵,需要JVM和OS(操作系统)配合完成大量的工作:
- 必须为线程堆栈分配和初始化大量内存块,其中包含至少1MB的栈内存。
- 需要进行系统调用,以便在OS(操作系统)中创建和注册本地线程。
线程池的作用:
- 提升性能:线程池能独立负责线程的创建、维护和分配。在执行大量异步任务时,可以不
需要自己创建线程,而是将任务交给线程池去调度。线程池能尽可能使用空闲的线程去执行异步任
务,最大限度地对已经创建的线程进行复用,使得性能提升明显。 - 线程管理:每个Java线程池会保持一些基本的线程统计信息,例如完成的任务数量、空闲
时间等,以便对线程进行有效管理,使得能对所接收到的异步任务进行高效调度。
详细线程池请参考线程池篇详细笔记
参考自:
- 《Java高并发核心编程 2 》
- 传智播客《深入学习Java并发编程与JUC》