​Java 线程的生命周期 Thread类的使用

​java线程 ​线程生命周期 Thread类如何使用

 

王皓的GitHub:https://github.com/TenaciousDWang

                    微信公众号

 

    上一回说了线程的创建,现在我们来说一下线程的生命周期。首先我们先看一下线程在不同生命时期都有哪些状态。

 

    线程从创建到终止,包括以下这几个状态:新建状态(NEW)、就绪状态(RUNNABLE)、执行状态(RUNNING)、阻塞状态(BLOCKED)、终止状态(DEAD)。

 

 

  上图镇楼。线程在新建状态时,虽然创建了但是处于未执行状态,当调用start()方法时,线程进入就绪状态。

 

 

线程的内存共享进程的内存及所有资源,且拥有自己的操作栈,程序计数器,局部变量表等,这些都是私有的。当满足条件后才会进入就绪状态。

 

进入就绪状态后,需要等待获得CPU执行时间,由于CPU可能正在执行其他任务,当获得执行时间后,进入执行状态,注意start()方法不能被多次调用,否则会抛出IllegalStateException异常。

 

 

运行状态即run()方法正在执行的状态,线程可能会由某些因素而退出,比如被同步块阻塞,用户主动操作睡眠,等待,这时线程会进入阻塞状态,当线程执行完毕或因为异常中断则直接进入终止状态。

 

以前我们说过并发的概念,知道CPU不会一直执行一个线程,而是轮流交替执行,同理我们的JVM虚拟机中的多线程也是通过线程轮流切换并分配处理器执行时间片的方式来执行的,所以为了切换线程后能恢复到正确的执行位置,每一个线程都需要记录程序计数器和CPU寄存器状态,需要注意,存储和恢复每个线程的执行状态,都是是资源开销,所以在进行多线程编程时,需要注意根据情况使用数量合理的线程数。

 

接下来我们来说一下Thread类里的方法,先来说一下start和run这两个方法,之前我们在创建线程时,是通过调用start来启动线程的,这个时候会为该线程分配,并让JVM调用该线程的run方法。

 

 

run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

 

       

sleep方法有两个重载,供用户让线程主动睡眠,交出CPU,让它去执行其他任务。

 

 

第一个sleep只有一个时间的参数,睡眠多久,单位为毫秒。

 

 

第二个sleep有两个时间的参数,第一个单位为毫秒,第二个为纳秒,可以看看源码,真相了,用不上。

 

但是注意,sleep并不会释放锁,这里我们用同步代码块,用同一个锁object,看看一个线程睡眠后,第二个线程会不会执行。首先先写一个SleepThread类继承Thread后重写run()方法。

 

 

然后写一个SleepTest,来创建并启动线程。

 

 

先来看一下结果。

 

 

第一个线程执行时,进入休眠状态后,并没有释放object,让第二个线程执行。

        

yield()方法,向调度程序提示,当前线程愿意放弃当前对处理器的使用,交出CPU执行时间片使用。

 

 

这里调用yield()方法并不会使该线程进入阻塞状态,而是暂时交出CPU执行时间片,回到就绪状态等待获得CPU执行时间片。

 

接下来说一下join方法。join方法共有三种重载。

 

 

线程调用join方法时,如果带参,main线程会等待至参数时间结束再继续执行,如果是调用不带参的join,则main线程会等待该线程执行结束后再执行,例子如下。

 

创建一个JoinThread线程。

 

   

创建测试main方法。

 

 

中间让thread线程调用join,控制台输出如下,main线程开始执行后,thread线程调用join方法,main线程在thread线程执行结束后,才恢复执行。

 

 

接下来说一下interrupt()方法,这个方法可以中断线程,注意这里可以中断的是进入阻塞状态的线程,下面我们来举一个栗子。

 

创建一个InterruptThread线程,使用sleep睡眠5秒。

 

然后创建一个InterruptTest类来测试。

 

 

当我们启动InterruptThread线程后,线程进入睡眠阻塞状态,这时,我们执行interrupt()方法,线程直接中断,run方法执行完毕。来看一下控制台结果。

 

 

上面举的是阻塞状态下的线程中断,但是正在执行中的线程无法中断,但是我们可以自定义一个isStop属性,写一个setStop方法从外面来中断,首先我们创建一个IsInterruptThread类。

 

  

里面定义一个isStop属性,然后写一个setStop方法来设置boolean属性决定while循环是否继续执行,接下来创建main方法执行·。

 

 

结果如下:

 

 

当打印到第五次时,不再继续执行while循环。

 

Thread类还有一个stop()方法与destroy()方法,这两个方法及其重载方法都已经废弃。

 

 

  接下来是几个获取和设置Thread属性的方法,getId用来得到线程ID,getName和setName用来得到或者设置线程名。

 

getPriority和setPriority用来获取和设置线程优先级。Java线程可以有优先级的设定,高优先级的线程比低优先级的线程有更高的几率得到执行(不完全正确,请参考下面的“线程优先级的问题“)。

 

  1. 记住当线程的优先级没有指定时,所有线程都携带普通优先级。
  2. 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
  3. 记住优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
  4. 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
  5. 由调度程序决定哪一个线程被执行。
  6. 记住在线程开始方法被调用之前,线程的优先级应该被设定,start之后再设定会抛出IllegalArgumentException。
  7. 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级。

 

不要假定高优先级的线程一定先于低优先级的线程执行,不要有逻辑依赖于线程优先级,否则可能产生意外结果。

 

setDaemon和isDaemon用来设置线程是否成为守护线程和判断线程是否是守护线程。Java分为两种线程:用户线程和守护线程。

 

守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。在JVM中,像垃圾收集器线程就是守护线程。

 

守护线程和用户线程没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。

 

在使用守护线程时需要注意一下几点:

 

  1. thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
  2. 在Daemon线程中产生的新线程也是Daemon的。
  3. 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

 

 

如果用户线程全部退出了,只剩下守护线程存在了,虚拟机也就退出了。

 

Thread类有一个比较常用的静态方法currentThread()用来获取当前线程。

 

最后再上一次线程状态及状态切换方法图解,Excel纯手工绘制:

     

竖着来张大图:

 

 

        这一篇比较长,写了好久 ~

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值