多线程基础知识学习总结
线程的历史 —— 一部对CPU压榨的血泪史
1、单线程人工切换(纸带机)
CPU利用效率低很多时间是等着人去换各种纸带
2、多进程批处理(多个任务批量执行)
但是如果其中一个任务等待的话也会导致其他任务跟着等待
3、多进程并行处理(把程序写在不同的内存位置上来回切换)
4、多线程(一个程序内部不同任务的来回切换)
5、纤程/协程(绿色线程,用户管理的【而不是OS管理的】线程)
线程进程概念
首先看上面这个计算机的组成,
程序:
可执行文件。
进程:
首先双击我们电脑的一个程序比如QQ.exe,它会经过操作系统并把当前程序信息加载到内存中,内存中就有一个正在运行的QQ.exe,我们也可以再次点击QQ.exe登录另外的账号,所以一个程序是可以在内存里面放很多份的,在内存里面的每一份都可以称为一个进程。
进程就是操作系统分配资源的基本单位。(静态概念)
线程:
依然如上图,我们之前说点击QQ.exe,双击时我们的程序会进入到内存中变成一个进程,真正开始执行的时候程序是以线程为单位来执行的,操作系统会找到我们的主线程扔给CPU执行,然后如果主线程开启了其他的线程,CPU会在这些线程中来回切换。
概念上:线程就是CPU调度执行的基本单位(动态概念:多个线程共享同一个进程里的所有资源)
线程切换(底层角度理解):
ALU:计算单元
Registers:寄存器组(用于存放数据)
PC寄存器(存储到底执行到哪条指令了)
cache:缓存
所以当T1线程要运行的时候把T1的数据和指令放在CPU中,然后CPU计算单元对他进行计算,计算好了该做输出做输出,该做其他操作做其他操作。
但是根据操作系统调度算法的话,T1线程执行到一半把当前的CPU时间片用完了那么就会将T1的指令和数据放在缓存中,然后切换将T2的指令和数据放在CPU中继续做计算。如果T2的执行时间到了,就将T2的指令和数据放缓存里,再从缓存里面读取T1的数据和指令(当然这中间需要操作系统的调度切换【线程上下文切换】)。
单核CPU设定多线程是否有意义?
有意义,比方说我的某一个在CPU执行的任务需要等待网络响应(等待的过程不消耗CPU资源),那么等待的这段时间如果设置了多线程的话,就可以切换到其他线程去执行任务,这样就可以充分利用CPU的资源。
CPU密集型:大量时间做计算。
CPU的IO密集型:大量时间在等待。
工作线程数是否设置的越大越好?
不是,这样导致CPU资源消耗在对线程的切换上面去了
public class Main {
//===================================================
private static double[] nums = new double[10_0000_000];
private static Random r = new Random();
private static DecimalFormat df = new DecimalFormat("0.00");
static {
for (int i = 0; i < nums.length; i++) {
nums[i] = r.nextDouble();
}
}
private static void m1() {
long start = System.currentTimeMillis();
double result = 0.0;
for (int i = 0; i < nums.length; i++) {
result += nums[i];
}
long end = System.currentTimeMillis();
System.out.println("m1: " + (end - start) + " result = " + df.format(result));
}
//=======================================================
static double result1 = 0.0, result2 = 0.0, result = 0.0;
private static void m2() throws Exception {
Thread t1 = new Thread(() -> {
for (int i = 0; i < nums.length / 2; i++) {
result1 += nums[i];
}
});
Thread t2 = new Thread(() -> {
for (int i = nums.length / 2; i < nums.length; i++) {
result2 += nums[i];
}
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
result = result1 + result2;
long end = System.currentTimeMillis();
System.out.println("m2: " + (end - start) + " result = " + df.format(result));
}
//===================================================================
private static void m3() throws Exception {
final int threadCount = 10000;
Thread[] threads = new Thread[threadCount];
double[] results = new double[threadCount];
final int segmentCount = nums.length / threadCount;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
int m = i;
threads[i] = new Thread(() -> {
for (int j = m * segmentCount; j < (m + 1) * segmentCount && j < nums.length; j++) {
results[m] += nums[j];
}
latch.countDown();
});
}
double resultM3 = 0.0;
long start = System.currentTimeMillis();
for (Thread t : threads) {
t.start();
}
latch.await();
for(int i=segmentCount*threadCount;i<nums.length;i++)
{
resultM3+=nums[i];
}
for (int i = 0; i < results.length; i++) {
resultM3 += results[i];
}
long end = System.currentTimeMillis();
System.out.println("m3: " + (end - start) + " result = " + df.format(resultM3));
}
public static void main(String[] args) throws Exception {
m1();
m2();
m3();
}
}
运行结果如下所示:
在m3方法中设置了10000个线程,但是它的执行时间竟然花了1000多ms,效率反而大大的降低了。
工作线程数(线程池数)设多少合适?如何做计算?
1、根据CPU的核数来设定到底需要多少个线程,还是以上面的例子为例首先通过 Runtime.getRuntime().availableProcessors() 方法获取本电脑到底有多少个核数,我这里的电脑核心数为6,再将m3方法的threadcount设置为6,让每一个核心都进行计算得到的运行时间如下:
m1: 106 result = 50000558.33
m2: 60 result = 50000558.33
m3: 48 result = 50000558.33
当然如上看不了太明显的差异,可以把数值继续往上调则能看到更明显的差距。
但是在程序运行的过程中,在一台机器上并不是仅仅是跑我们的程序,还有很多其他的程序在运行。所以上面设置6并不一定代表能充分运行上所有的线程,得看实际运行的情况。一般程序中给CPU的余量在80%左右。
线程数量设定公式
N(threads) = N(CPU)U(CPU)(1+W/C)
1、N(CPU)是处理器的核的数目,可以通过Runtime.getRuntime().availableProcessors()得到
2、U(CPU)是期望的CPU利用率(介于0-1之间)
3、W/C是等待时间与计算时间的比率
W/C如何得到
1、用profiler做计算
2、Java常用的是Jprofiler
3、如果部署到了远程采用Arthes工具