Java 并发编程线程池机制 ( 测试线程开销 | 启动线程分析 | 用户态 | 内核态 | 用户线程 | 内核线程 | 轻量级进程 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 并发编程线程池机制 ( 测试线程开销 | 启动线程分析 | 用户态 | 内核态 | 用户线程 | 内核线程 | 轻量级进程 )相关的知识,希望对你有一定的参考价值。
一、测试线程开销
线程池是线程的缓存 , 在 Java 高并发场景中 , 所有的异步操作 , 都可以使用线程池 ;
使用线程池时 , 不建议用在 " 执行耗时较长的操作 " 的业务场景中 ;
线程池机制 最重要的功能就是 复用线程 ; 线程的创建 , 销毁 , 都是要消耗资源的 , 如果频繁创建销毁线程 , 会消耗很多资源 ;
1、正常测试
下面开始测试一下线程创建的开销 :
在主线程中 , 启动
10
10
10 万个线程 , 每个线程中累加 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);
}
}
执行结果 : 10 10 10 万个线程执行完毕消耗 10.992 10.992 10.992 秒 ;
2、不创建线程
注释掉线程相关代码 :
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);
}
}
执行结果 :
1
1
1 ms 执行完毕 ; 说明耗时操作是在 for
循环中 ;
3、只创建不启动线程
注释掉线程启动代码 :
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);
}
}
执行结果 : 耗时 79 79 79 ms , 也很快 , 大部分时间都在 启动 与 等待线程执行完毕消耗 ;
4、只启动不等待执行完成
注释掉等待线程执行完成代码 :
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);
}
}
执行结果 : 耗时 3.866 3.866 3.866 秒 ;
二、分析测试结果
1、启动线程分析
在上述测试中 , 如果只是创建
10
10
10 万个 Thread
对象 , 这些在 Java 中就是普通的对象 ;
但是如果调用了 Thread
对象的 start()
方法 , 就要涉及到系统的线程切换 , 这个操作非常耗时 ;
操作系统的空间 , 分为 用户空间 和 内核空间 ;
用户空间中 , 有多个进程 , 每个进程有多个线程 , 每个进程都有一个 线程表 , 用于保存该进程中的线程 ;
JVM 创建的线程是 内核线程 ;
执行 main
函数时 , 处于 用户态 , 一旦调用了 start()
方法启动了线程 , 此时就进入了 内核态 , 该状态切换消耗巨大 ;
2、用户线程与内核线程
系统的线程分为 用户线程 和 内核线程 ;
用户线程 : 用户线程是 用户程序实现的线程 , 并负责管理线程的 创建 , 执行 , 调度 , 同步 ;
- 线程阻塞时 , 进程也会阻塞 ;
( Java 没有用到用户线程 )
内核线程 : 内核线程是 由内核管理的线程 , 其内部保存了线程的状态信息 , 上下文信息 , 如果频繁的切换线程 , 需要反复处理状态信息 , 上下文信息 , 会浪费很多资源 ;
- 线程阻塞时 , 进程不会阻塞 ;
- 内核线程效率比用户线程低 , 比进程高 ;
3、轻量级进程
轻量级进程 : 在我们写的程序中 , 虽然使用了内核线程 , 但 没有直接使用 , 而是 通过内核线程的高级接口使用内核线程 , 这个高级接口就是 " 轻量级进程 " , Java 程序中的 Thread
就是轻量级进程 , 每个 轻量级进程 都对应一个 内核线程 ;
4、验证 Java 线程类型
在任务管理器中可以查看线程数 :
执行下面的程序 : 创建了 10000 10000 10000 个线程
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();
}
}
}
创建 10000 10000 10000 线程后 , 发现线程数增加了 10000 10000 10000 ;
由此可见 , Java 虚拟机创建的线程是内核线程 ;
Java 虚拟机创建线程 , 依赖于系统内核 , 内核空间的内核线程 与 用户空间的 Java 线程 是一一对应的关系 ;
以上是关于Java 并发编程线程池机制 ( 测试线程开销 | 启动线程分析 | 用户态 | 内核态 | 用户线程 | 内核线程 | 轻量级进程 )的主要内容,如果未能解决你的问题,请参考以下文章
Java 并发编程线程池机制 ( 线程池阻塞队列 | 线程池拒绝策略 | 使用 ThreadPoolExecutor 自定义线程池参数 )