线程池是线程的缓存 , 在 Java 高并发场景中 , 所有的异步操作 , 都可以使用线程池 ;
使用线程池时 , 不建议用在 " 执行耗时较长的操作 " 的业务场景中 ;
线程池机制 最重要的功能就是 复用线程 ; 线程的创建 , 销毁 , 都是要消耗资源的 , 如果频繁创建销毁线程 , 会消耗很多资源 ;
下面开始测试一下线程创建的开销 :
在主线程中 , 启动
万个线程 , 每个线程中累加 count
变量 ;
public class Main {
/**
* 线程中对该值进行累加操作
*/
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 记录程序开始执行时间
long startTime = System.currentTimeMillis();
// 创建 10 万个线程, 开启线程后, 向集合中添加一个元素
for (int i = 0; i < 100000; i ++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
count ++;
}
});
// 启动线程
thread.start();
// 等待线程执行完成
thread.join();
}
// 记录程序执行结束时间
long endTime = System.currentTimeMillis();
// 打印消耗的时间
System.out.println("耗时 : " + ( endTime - startTime ) + " ms , 最终 count = " + count);
}
}
执行结果 :
万个线程执行完毕消耗
秒 ;
注释掉线程相关代码 :
public class Main {
/**
* 线程中对该值进行累加操作
*/
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 记录程序开始执行时间
long startTime = System.currentTimeMillis();
// 创建 10 万个线程, 开启线程后, 向集合中添加一个元素
for (int i = 0; i < 100000; i ++) {
/*Thread thread = new Thread(new Runnable() {
@Override
public void run() {
count ++;
}
});
// 启动线程
thread.start();
// 等待线程执行完成
thread.join();*/
}
// 记录程序执行结束时间
long endTime = System.currentTimeMillis();
// 打印消耗的时间
System.out.println("耗时 : " + ( endTime - startTime ) + " ms , 最终 count = " + count);
}
}
执行结果 :
ms 执行完毕 ; 说明耗时操作是在 for
循环中 ;
注释掉线程启动代码 :
public class Main {
/**
* 线程中对该值进行累加操作
*/
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 记录程序开始执行时间
long startTime = System.currentTimeMillis();
// 创建 10 万个线程, 开启线程后, 向集合中添加一个元素
for (int i = 0; i < 100000; i ++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
count ++;
}
});
/*// 启动线程
thread.start();
// 等待线程执行完成
thread.join();*/
}
// 记录程序执行结束时间
long endTime = System.currentTimeMillis();
// 打印消耗的时间
System.out.println("耗时 : " + ( endTime - startTime ) + " ms , 最终 count = " + count);
}
}
执行结果 : 耗时
ms , 也很快 , 大部分时间都在 启动 与 等待线程执行完毕消耗 ;
注释掉等待线程执行完成代码 :
public class Main {
/**
* 线程中对该值进行累加操作
*/
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 记录程序开始执行时间
long startTime = System.currentTimeMillis();
// 创建 10 万个线程, 开启线程后, 向集合中添加一个元素
for (int i = 0; i < 100000; i ++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
count ++;
}
});
// 启动线程
thread.start();
// 等待线程执行完成
//thread.join();
}
// 记录程序执行结束时间
long endTime = System.currentTimeMillis();
// 打印消耗的时间
System.out.println("耗时 : " + ( endTime - startTime ) + " ms , 最终 count = " + count);
}
}
执行结果 : 耗时
秒 ;
在上述测试中 , 如果只是创建
万个 Thread
对象 , 这些在 Java 中就是普通的对象 ;
但是如果调用了 Thread
对象的 start()
方法 , 就要涉及到系统的线程切换 , 这个操作非常耗时 ;
操作系统的空间 , 分为 用户空间 和 内核空间 ;
用户空间中 , 有多个进程 , 每个进程有多个线程 , 每个进程都有一个 线程表 , 用于保存该进程中的线程 ;
JVM 创建的线程是 内核线程 ;
执行 main
函数时 , 处于 用户态 , 一旦调用了 start()
方法启动了线程 , 此时就进入了 内核态 , 该状态切换消耗巨大 ;
系统的线程分为 用户线程 和 内核线程 ;
用户线程 : 用户线程是 用户程序实现的线程 , 并负责管理线程的 创建 , 执行 , 调度 , 同步 ;
( Java 没有用到用户线程 )
内核线程 : 内核线程是 由内核管理的线程 , 其内部保存了线程的状态信息 , 上下文信息 , 如果频繁的切换线程 , 需要反复处理状态信息 , 上下文信息 , 会浪费很多资源 ;
轻量级进程 : 在我们写的程序中 , 虽然使用了内核线程 , 但 没有直接使用 , 而是 通过内核线程的高级接口使用内核线程 , 这个高级接口就是 " 轻量级进程 " , Java 程序中的 Thread
就是轻量级进程 , 每个 轻量级进程 都对应一个 内核线程 ;
在任务管理器中可以查看线程数 :
执行下面的程序 : 创建了
个线程
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 10000; i ++){
new Thread(()->{
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
创建
线程后 , 发现线程数增加了
;
由此可见 , Java 虚拟机创建的线程是内核线程 ;
Java 虚拟机创建线程 , 依赖于系统内核 , 内核空间的内核线程 与 用户空间的 Java 线程 是一一对应的关系 ;