本文主要以实战的方式,探索Java语言中的Thread与OS中进程(线程)的关系,观察OS创建线程的细节,并总结其中的资源消耗。
通过这个过程,可以了解到操作系统是如何支持Java线程的。
随处可见的论调
在生产环境中,为每个任务分配一个线程的做法存在一些缺陷,尤其当并发量很高的时候。
- 创建和销毁线程的代价相当高。
- 活跃的线程会消耗系统资源,尤其是内存。
- 稳定性难以保证。可创建线程的数量受多个条件限制,包括JVM的启动参数、Thread构造函数中请求的栈大小以及底层操作系统的限制。
基于此,线程池的优势就体现出来了。线程池是一种基于池化思想管理线程的工具,与String的字符串缓冲池、Integer的IntegerCache、数据库的连接池等一样,都是享元模式的典型应用。优点主要有以下三点:
- 复用线程对象。节省了大量创建和销毁线程的开销。
- 减少响应时间。每个任务被提交之后,通常状况下,有创建好的线程执行它,不需要即时创建线程。
- 统一管理线程。
以上论调基本回答了本文的主题,为什么要使用线程池。可是只有理论毕竟流于表面,我还有两个疑问。
- Java中的线程和OS中的进程(线程)的对应关系是怎样的?也就是,java中的线程和OS的线程是一对一还是多对一。很自然地,如果是多对一,大量java线程只需要少量OS线程支持,那么大量创建Java线程,OS资源消耗也许没有太大。
- 对于高并发场景,线程数量是不是越多越好?线程数量太多会有哪些方面的不良影响?
以下实战环节,主要是探索第一个问题,这也是本文的主题。顺带根据一些实验现象,分析得出第二个问题的答案。
实战
一、验证java线程与OS线程对应关系
实验环境:
- linux内核:3.1
- CPU
- 物理槽位:2
- 每槽位核数:1
- 每核线程数:1
实验素材:
-
一个计算密集型任务 : 求斐波那契数列的第n项。
//递归,求斐波那契数列第n项 public static long fib(int n){ if(n == 1 || n == 2){ return 1; } return fib(n-1) + fib(n-2); }
-
下面的代码只创建一个线程。该线程求解斐波那契数列的第50项。
import java.io.IOException; public class CreateOneThread { public static void main(String[] args) throws IOException { System.out.println("BLOCKING..."); System.in.read();//阻塞1 Thread t1 = new Thread(() -> { long start = System.currentTimeMillis(); System.out.println(fib(50));//求解斐波那契数列中第50个数 System.out.println("total time:\t" + (System.currentTimeMillis() - start)); }); System.out.println("PRESS ENTER TO START..."); System.in.read();//阻塞2 t1.start(); } }
-
下面的代码创建1k个线程。每一个线程求解斐波那契数列的第50项。
import java.io.IOException; public class CreateMultiThreads { public static void main(String[] args) throws IOException { Syste