02. 就该这么学并发 - 线程的就绪与执行
前言
接上章“线程的创建
”, 本章我们聊聊线程生命周期的“就绪
”和“运行
”状态.
就绪(Runnable)
线程对象的start()
方法被调用后,该线程即进入就绪状态
- 此时JVM会为其创建方法调用栈和程序计数器;
- 该状态的线程一直处于"线程就绪队列"
尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列,
因为CPU的调度不一定是按照先进先出的顺序来调度的
- 此时线程等待系统为其分配CPU时间片,并不是说执行了start()方法就立即执行
简而言之,
假设有子线程t, 那么“t.start()
”,
并不是我们以为的“子线程t立刻就执行了
”,
而是“告诉CPU: 子线程t在等待执行了,随时等你的调度
”
可以让子线程调用“start()”后立刻执行么?
这里其实要分情况讨论
- 只有主线程, 和一个就绪的子线程
程序可以使用Thread.sleep(1) 来让当前运行的线程(主线程)睡眠1毫秒,
1毫秒就够了,因为在这1毫秒内CPU不会空闲,它会去执行另一个处于就绪状态的线程,
这样就可以让子线程立即开始执行;
- 有多个就绪的线程
那么情况就复杂的多, 我们可能需要使用“sleep()”,“wait()”,“yield() ”,“setPriority() ”等来干预,
确保同一时刻只有一个就绪的子线程.
对于线程的控制, 大家一定要记得一句话
我们并不能精确的控制线程执行, 只能粗略的干预
运行(Running)
当就绪的线程获得CPU的时间片,线程就进入运行状态并自动调用自己的run()方法
run()方法定义了线程的操作和功能
- 如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态;
- 如果在一个多处理器的机器上,将会有多个线程并行执行,处于运行状态;
- 当线程数大于处理器数时,依然会存在多个线程在同一个CPU并发执行现象(轮换执行);
并发并行
很多人可能对并发并行的概念有疑惑,这里就简单的介绍下
并发
一个处理器同时处理多个任务(某一时刻只有一个任务在处理)
并行
多个处理器(或者多核的处理器)同时处理多个不同的任务(某一时刻多个任务在处理)
前者是逻辑上的同时发生
而后者是物理上的同时发生
来个比喻:
并发就是一个人同时吃三个馒头
并行就是和三个人同时吃三个馒头
并发解决的是线程阻塞造成的不流畅问题
并行才是真正解决运行效率的问题
t.start()和t.run()区别
学完此章, 可能又同学看到线程的“start()”和“run()”方法,又疑惑了, 这两个字面意思好像都是“开始执行”, 有啥区别?
我们创建一个线程
ExtendsThread t = new ExtendsThread();
其实,t.start()方法执行后,本质上也是调用run()函数体方法
,
我们举个例子
t.start()
public class ImpRunnableThread implements Runnable {
@Override
public void run() {
System.out.println("运行---->" + Thread.currentThread().getName() + "<-----");
}
}
class Test {
public void main(String[] args){
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "创建了一个:线程" + i);
ImpRunnableThread r = new ImpRunnableThread();
Thread t = new Thread(r,"线程" + i);
t.start(); //t.run();
}
}
}
运行
main创建了一个:线程0
main创建了一个:线程1
运行---->线程0<-----
main创建了一个:线程2
运行---->线程1<-----
main创建了一个:线程3
运行---->线程2<-----
main创建了一个:线程4
运行---->线程3<-----
运行---->线程4<-----
t.run()
main创建了一个:线程0
运行---->main<-----
main创建了一个:线程1
运行---->main<-----
main创建了一个:线程2
运行---->main<-----
main创建了一个:线程3
运行---->main<-----
main创建了一个:线程4
运行---->main<-----
对比两种结果,我们可以发现输出的异同
因此得出结论
-
t.start()和t.run()都运行了run()内的代码
-
t.start()是真的启动了相应的线程0-4
,而t.run()并没有真的启动线程0-9,而是由一个叫main的主线程去调用的run()内的代码
. -
t.start()方法具有异步执行的效果,而使用t.run()方法是同步执行的效果
有时候t.start()运行结果上看起来也是同步的,
多运行几次,或者增加线程数就能看出区别,
原因就在于:
t.start()是真正让子线程在执行,
而子线程何时运行是由JVM调度的,所以执行是无序的;
而t.run()则直接在单线程main上执行,所以是有序的!
所以,我们一定要使用start()方式来运行线程!!
main线程是什么?
搞懂了t.start()和t.run()的区别,
可能有朋友会对刚刚的demo输出有疑惑了?
main创建了一个:线程0
运行---->main<-----
main创建了一个:线程1
这个 main是个什么???
这个时候我们就得联想到进程的概念了,前面有说过,
进程必须至少有一个线程
如何保证这个“至少一个
”呢?
显然,JVM启动时则自动帮我们创建了这个线程
,名字就叫“main
”
这一块的内容后续学习JVM时我们再进一步探究
main线程和其它线程几乎没有什么不同
唯一的不同就是
main线程是个非守护线程,不能通过线程对象的setDaemon(true)设置成守护线程
守护线程就是个无限循环的线程,用来执行一些监控,定时触发的任务
和其他线程一样,main线程的死亡,也不会对其它线程产生任何影响
欢迎关注我
技术公众号 “CTO技术”
