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线程的死亡,也不会对其它线程产生任何影响

请关注我的订阅号

订阅号.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码哥说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值