一、 start、sleep 和 join 的理解
无论是什么样的开发语言的多线程,都沿用这样的规则 —— start 启动线程,sleep 暂停当前父线程的运行,即执行到了第 10 行代码,遇到 sleep 就会停止执行第 11 行代码,join 会让父线程等待子线程,否则正常情况下,父线程会自顾自的执行,自顾自的结束运行,不会管子线程是否执行完毕,直接退出,释放父线程内存,由于多线程内存共享原理,释放父线程内存,就是在释放子线程的内存,当然这里不会像 sleep 一样,会停止执行父线程的代码,依旧会运行父线程的代码,只不过会等待用过 join 的子线程,等待它们都执行完毕了,也就是说父线程不会放它们这些子线程鸽子,都会等待它们到海枯石烂。
这里笔者见识不大,个人理解。
二、多线程启动顺序影响性能问题
举例:
多线程逻辑:
开启多线程,使用多线程数组,存储这些线程实例对象,然后一个一个的 start
启动它们,然后一个一个的 join
它们,否则太多子线程了,父线程不等所有的子线程执行完毕,就撂担子了,那就尴尬了。
1、设计思路 1 不想让系统太卡的多线程设计
1号类型的任务,依据逻辑推敲,可能会占用较大的资源消耗,需要执行 n 次,消耗单位为 2
2号类型的任务,依据逻辑推敲,可能不会占用较大的资源消耗,需要执行 n 次,消耗单位为 1
那么比较好的安排是 1 号和2号类型任务组成一个多线程数组,然后 start 执行这两个线程
start 1 号任务 和 2 号任务
消耗单位 2 加 消耗单位 1 = 需要的消耗单位 3
由于父线程并没有 sleep,所以依旧会执行 n 次上面的步骤,也就是说在第 1 次start 时,后面第 2 次也会跟着 start ,只不过 第 1 次的 start 会快过 第 2 次的 start ,中间就会有一个缓冲的时间,不会一次性的将 n 次的 1 号和 2 号任务,一起执行,瞬间会创建这些 n 次的执行任务线程内存,不会瞬间加大内存的压力。
以 Java
为例:下面执行 10000
次 1号和 2 号线程任务
Test1 1号任务
Test2 2号任务
给 Test 1 和 Test2 线程任务传递参数 i,从 0 到 9999,共 10000
次
int times = 10000;
for(int i=0;i<times;i++){
List<Thread> threads = new ArrayList<>(2);
threads.add(new Thread(new Test1(i));
threads.add(new Thread(new Test2(i));
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
}
优点:
在不会瞬间导致电脑或系统的内存或 CPU 占用过大的情况下,可以考虑这种多线程设计,也就是说不想让电脑或系统很卡时,就可以考虑这种方法。
还有如果Test1 或 Test2 任务 run 方法内,涉及到网络的什么 token 获取或者是网络的接口,那么这个方法是挺不错的,起码可以有一个顺序执行的感觉。
缺点:
不好说,笔者没具体测试
2、随意,只想暴力执行,有多快就怎么来
Java
代码:
int times = 10000;
List<Thread> threads = new ArrayList<>(times);
for(int i=0;i<times;i++){
threads.add(new Thread(new Test1(i));
threads.add(new Thread(new Test2(i));
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
这就挺大气的,直接启动 10000
个线程,不怕资源的瞬间消耗,系统可能出现的卡顿,如果是跑爬虫,机械学习之类的,当我没说。
这个多线程的执行,就不考虑什么线程顺序执行了,完全就是怎么快怎么来,如果存在网络接口的代码执行,那么就会有一个网络接口访问频繁,导致的网络接口禁用的问题,即访问这个网络接口不通的问题,需要等待一段时间,让这个网络接口释放我们的访问 IP,才可以继续访问这个网络接口,当然有钱的话,也可以用代理转发,这个问题也是可以解决的。