多线程编程3——线程的状态

一、状态是线程的状态

状态是PCB中与调度相关的属性线程是CPU调度执行的基本单位。所以,状态是线程的属性谈到状态,考虑的都是线程的状态,不是进程!!!

二、在Java中,线程的状态有哪几种?

在Java中,线程的状态比操作系统内核中的更加详细, 线程的状态有以下六种:

状态说明
NEW创建了Thread对象,但没调用start(内核中还没有创建对应的PCB)
TERMINATED内核中的PCB已经执行完销毁了,Thread对象还在
RUNNABLE可运行的(正在CPU上执行的 和 在就绪队列中随时可以去CPU上执行 这两种统一都叫做RUNNABLE状态,不做区分,也区分不了)
WAITING阻塞(线程PCB正在阻塞队列中)
TIMED_WAITING阻塞(线程PCB正在阻塞队列中)
BLOCKED阻塞(线程PCB正在阻塞队列中)

对这六种状态,下面进行详细介绍:

1、首先, 主线任务是,NEW,RUNNABLE,TERMINATED

比如下面的代码(当run方法中没有sleep或其他会造成线程阻塞的代码时):

刚开始,创建了Thread对象,但还没执行到start方法,这时,线程的状态就是NEW。

接着,调用start创建线程,当线程参与调度,这时,线程的状态就是RUNNABLE。

接着,主线程调用join方法,阻塞等待线程执行结束,当线程执行完run方法,线程就结束了,PCB就释放了,但是 t对象还在,这时,线程的状态就是TERMINATED 

 对于TERMINATED这个状态,我再啰嗦几句:

这个状态是不得已而存在的,是为了把 PCB已经释放的 线程对象 标识成无效。

当线程结束,内核中的PCB就释放了,但因为Java中对象的生命周期自有其规则,和PCB的生命周期并非完全一致。所以PCB释放时,无法保证Java代码中的 t对象也释放了。 那么就一定会存在,内核中的PCB已经释放,但代码中 t对象还存在的这种情况。

所以我们就需要一个特定的状态把 t对象标识成无效,让程序员知道,该对象已经没用了

避免出现线程已经结束了,但由于t对象还在,误以为线程还存在,还使用该线程进行多线程编程的情况。这就比较难受了。

所以,TERMINATED这个状态必须得存在,没有办法。

当线程的状态是TERMINATED时,不能使用该线程进行多线程编程了但是,对象还在,所以我们可以调用对象中的方法和属性的,这并不冲突。

举例如下:(这些 对象的方法都可以调用,完全没问题)

2、线程在RNUUABLE状态下因为一些原因导致线程进入阻塞状态,即进入支线任务。当线程在执行run方法过程中,因为各种原因产生阻塞,这时,线程状态就会发生改变,就不会一直处于RUNNABLE状态了。

线程因为不同原因发生阻塞会进入不同的状态

(1)TIMED_WAITING:因为需要等待一定的时间产生的阻塞,sleep等类似方法时间到了,就从该状态中出来,回到RUNNABLE状态了

(2)WAITING:因为需要等待其他人来通知产生的阻塞,wait/join等方法条件满足,通知到了,就从该状态中出来,回到RUNNABLE状态了

(3)BLOCKED:因为等待锁产生的阻塞。获取到锁,就从该状态中出来,回到RUNNABLE状态了

下面我通过代码来演示一下TIMED_WAITING状态:

线程的run方法中有个循环,循环10万次,循环内部有个sleep,休眠10毫秒。当线程运行i<10_0000这样的代码时,线程的状态就是RUNNABLE;当处于sleep阻塞时,线程的状态就是TIMED_WAITING。

主线程的main方法中也有个循环,循环1千次,循环内部打印线程的状态。由于这个循环在start方法之后,所以打印的线程状态,要么是RUNNABLE(当线程在CPU上执行或在就绪队列中时),要么是TIMED_WAITING(当线程在阻塞等待时)。获取的状态是什么,取决于获取的那一瞬间,线程处于哪个状态(正在CPU上执行或在就绪队列中,还是在阻塞等待)。

但是为啥会先打印RUNNABLE,再打印TIMED_WAITING,且TIMED_WAITING打印的更多呢?

因为循环在start方法之后,如上图,在主线程打印线程的状态时,线程先是在CPU上执行i<10_0000这样的代码,然后才进入sleep等待。这样的过程会循环10_0000次。所以,当主线程循环打印1000次线程的状态时,先打印的状态是RUNNABLE,后打印的状态是TIMED_WAITING。因为sleep休眠10毫秒,虽然10ms对人来说很快,但对计算机来说还是很慢的。而线程在CPU上执行的代码却很少。线程在CPU上执行的时间远远小于线程sleep的时间,所以打印的TIMED_WAITING状态更多(进入sleep产生的阻塞就是TIMED_WAITING状态)。

三、多线程的意义到底是啥?

多线程最核心的地方:抢占式执行,随机调度

使用多线程编程能解决“并发编程”的问题,更好的利用了CPU的多核资源,提高执行速度。

下面我通过一个例子,让大家感受一下,单个线程和多个线程之间,执行速度的差别。

程序主要分成:CPU密集 和 IO密集 两种。CPU密集:包含了大量的加减乘除等算术运算。IO密集:涉及到读写文件,读写控制台,读写网络。

我们举一个CPU密集的例子,比如现在有一个运算量很大的任务,看一下单线程和多线程的区别。

假设当前有两个变量,需要把两个变量各自自增 100亿 次。 

单线程(只有主线程):先对 a自增,再对 b自增。

两个线程(主线程和线程):线程先对 a自增,再对 b自增。

三个线程(主线程,线程1,线程2):线程1 和 线程2 分别对 a和b 自增。

加个计时的操作来衡量代码的执行速度,System.currentTimeMillis() —— 获取到当前系统的 ms级时间戳。 

 

上述代码是一个线程完成的,只有主线程。

我们可以看到,执行时间大约是1万 ms 

 

 

上述代码是两个线程完成的,主线程 和 线程t。在主线程中调用start方法创建一个线程,这个线程负责先对 a自增,再对 b自增 。

我们可以看到,执行时间大约是 5000 ms

 

上述代码是三个线程完成的,主线程,线程t1 和 线程t2。线程t1 和 线程t2 分别对 a和b 自增。

我们可以看到,执行时间大约 3000 ms

刚开始,我将计时代码:long begin = System.currentTimeMillis();写在了两个start方法之后,发现结果有问题。  

为什么呢?为什么不能写在start后,要写在start前呢?

因为主线程,线程t1,线程t2 是并发执行的,CPU是随机调度的,哪个线程先去CPU上执行是随机的。如果把计时代码写在两个start 方法之后,在主线程还没执行到计时代码时,线程t1 或 线程t2 可能已经去执行run方法中的代码了。就不准了。总共的执行时间会被少算。

通过上面三个代码,观察发现:

只有主线程:10000 ms 

主线程,线程t:5000 ms

主线程,线程t1,线程t2:3000 ms

总结:多线程能充分利用CPU的多核资源,提高运行效率。 

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值