Java:线程状态及线程状态转换方法


前言

本文记录笔者关于Java多线程中,多线程状态及线程各种状态之间转换的方法

一、Java线程状态

关于Java线程状态,笔者在阅读网上博客时,得到线程状态分类有五类,也有六类,本篇博客笔者更倾向于按照五类的Java线程状态进行书写和记录

五类状态

其实关于Java线程状态分类没有统一的答案,只要能清楚表示出线程状态即可

具体的线程分类如下

  • new (新建),一个线程被创建了但未被启动就处于新建状态,即在程序中使用new MyThread();创建的线程实例就处于此状态。
  • runnable(可运行),创建的线程实例调用start()方法后便进入可运行状态,处于此状态的线程并不是说一定处于运行状态,Java多线程使用的线程调度策略是抢占式调度,每个可运行线程轮着获取CPU时间片,可以虚拟想象成有一个可运行线程池,start()方法把线程放进可运行线程池中,CPU按一定规则一个个执行池里的线程。
  • running (运行),当可运行线程获取到CPU执行时间片即进去了运行状态。
  • not runnable(非可运行),运行中的线程因某种原因暂时放弃CPU的使用权,可能是因为执行了挂起、睡眠或等待等操作,在执行I/O操作时由于外部设备速度远低于处理器速度也可能导致线程暂时放弃CPU使用权,在获取对象的同步锁过程中如果同步锁先被别的线程占用同样可能导致线程暂时放弃CPU。
  • dead(死亡),线程执行完run()方法实现的任务,或因为异常导致退出任务,线程进入死亡状态后将不可再转换成其他状态。

非可运行状态(not runnable)细分

在五类状态中,其他四类状态都比较明确,但是not runnable(非可运行)状态可进行进一步细分,按照是否释放对象锁和引发原因可继续引申细分为阻塞(blocked)、同步锁(locked)和等待状态(waiting)

  • 阻塞(blocked):阻塞由阻塞事件触发,线程处于阻塞状态将放弃CPU的使用权,暂时停止运行。一般线程执行了**sleep()、join()**方法,或发出了I/O请求,线程就将处于阻塞状态,假如sleep()执行的睡眠结束、join()执行的等待中断超时、I/O请求结束,则将重新回到可执行状态,等待分配CPU。
  • 同步锁(locked):假如一个线程准备调用一个同步方法,而同步方法对应的对象正被其他线程占用,此时线程就将进入同步锁状态。实际上,Java中的每个object对象都有一个monitor,此monitor负责对同步域在并发时的独占处理,即一个线程调用某对象的同步方法时,JVM将检测改对象的monitor是否已被占用,如果没有被占用,线程则得到monitor占有权,继续执行该对象的同步方法,否则线程将被扔进一个等待线程队列排队,直到monitor被释放后,所有等待的线程继续竞争monitor占有权,抢到monitor占有权后才进入可执行状态等待CPU的分配,才有资格执行同步方法。
  • 等待(waiting):运行中的线程执行了wait()方法后就进入等待状态。一个对象执行了wait()方法同样将使线程进入该对象的等待线程队列,同时它还将释放对象锁,即放弃monitor的占有权。只有在其他线程中对该对象调用notify()、notifyAll()方法时才会唤醒等待线程队列中的线程,notify是随机唤醒等待队列中的一个线程,而nofityAll则是唤醒所有等待队列中的线程,所有线程被唤醒后将对该对象的monitor占有权竞争,获取到占有权的线程才能转化为可执行状态,等待分配CPU往下执行,其他线程则继续等待。

二、线程状态的基本操作

1.interrupted

中断可以理解为线程的一个标志位,它表示了一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了一个招呼。其他线程可以调用该线程的interrupt()方法对其进行中断操作,同时该线程可以调用
isInterrupted()来感知其他线程对其自身的中断操作,从而做出响应。另外,同样可以调用Thread的静态方法
interrupted()对当前线程进行中断操作,该方法会清除中断标志位。需要注意的是,当抛出InterruptedException时候,会清除中断标志位,也就是说在调用isInterrupted会返回false。

2. join

  • join方法可以看做是线程间协作的一种方式,很多时候,一个线程的输入可能非常依赖于另一个线程的输出,这就像两个好基友,一个基友先走在前面突然看见另一个基友落在后面了,这个时候他就会在原处等一等这个基友,等基友赶上来后,就两人携手并进。其实线程间的这种协作方式也符合现实生活。在软件开发的过程中,从客户那里获取需求后,需要经过需求分析师进行需求分解后,这个时候产品,开发才会继续跟进。如果一个线程实例A执行了threadB.join(),其含义是:当前线程A会等待threadB线程终止后threadA才会继续执行。关于join方法一共提供如下这些方法:

3.sleep

  • public static native void sleep(long millis)方法显然是Thread的静态方法,很显然它是让当前线程按照指定的时间休眠,其休眠时间的精度取决于处理器的计时器和调度器。需要注意的是如果当前线程获得了锁,sleep方法并不会失去锁。sleep方法经常拿来与Object.wait()方法进行比价,这也是面试经常被问的地方。
sleep()和wait()的区别
  • sleep()方法是Thread的静态方法,而wait是Object实例方法
  • wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方种使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁;
  • sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。

4.yield

  • **public static native void yield();**这是一个静态方法,一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。什么是线程优先级了?下面就来具体聊一聊。
  • 现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当前时间片用完后就会发生线程调度,并等待这下次分配。线程分配到的时间多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要或多或少分配一些处理器资源的线程属性。
  • 在Java程序中,通过一个整型成员变量Priority来控制优先级,优先级的范围从1~10.在构建线程的时候可以通过**setPriority(int)**方法进行设置,默认优先级为5,优先级高的线程相较于优先级低的线程优先获得处理器时间片。需要注意的是在不同JVM以及操作系统上,线程规划存在差异,有些操作系统甚至会忽略线程优先级的设定。
  • 另外需要注意的是,sleep()和yield()方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep()交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而yield()方法只允许与当前线程具有相同优先级的线程能够获得释放出来的CPU时间片
    在这里插入图片描述

三、守护线程

守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默地守护一些系统服务,比如垃圾回收线程,JIT线程就可以理解守护线程。与之对应的就是用户线程,用户线程就可以认为是系统的工作线程,它会完成整个系统的业务操作。用户线程完全结束后就意味着整个系统的业务任务全部结束了,因此系统就没有对象需要守护的了,守护线程自然而然就会退。当一个Java应用,只有守护线程的时候,虚拟机就会自然退出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值