文档开发者中心
我的

Java并发4 - 线程的基本操作

本篇讲解线程的基本概念,包括线程是什么、线程的API及使用规范。

线程的状态

JDK中线程有五种状态:初始状态NEW、可运行状态RUNNABLE、运行状态RUNNING、阻塞状态BLOCKED及终止状态TERMINATED。这五种状态的关系如下图:

  1. 初始状态: 线程被创建,线程对象生成,即进入了初始状态NEW。这时只是生成了一个Java对象,操作系统还没有感知。

  2. 可运行状态: 线程启动后,操作系统已经感知,进入操作系统的线程调度框架,可以被分配CPU;

  3. 运行状态:当有CPU空闲时,操作系统线程调度系统分配CPU,线程进入运行状态,开始执行run代码;

  4. 阻塞状态:线程在执行过程调用一个阻塞API或等待某个事件时,线程的状态会进入阻塞状态,释放CPU;当执行条件得到满足后,如阻塞API返回、等待事件发生,线程重新时入可运行状态,等待操作系统分配CPU;

  5. 终止状态:线程结束运行,有三种情况可能初线程结束:run终止正常退出、收到stop/interrupt调用终止、线程出现异常。

线程状态的切换

以上状态是从操作系统的角度看的实际运行状态,Java语言层面的实现与此略有差异,Java语言中线程一共有六种状态,分别是:

  1. 初始化状态 NEW;

  2. 可运行/运行状态 RUNNABLE;

  3. 阻塞状态 BLOCKED;

  4. 无限时等待状态 WAITING;

  5. 有限时等待状态 TIMED_WAITING;

  6. 终止状态 TERMINATED;

Java把OS的可运行与运行状态合一,因为这个语言层面上控制不了;把阻塞状态拆分成三种状态:BLOCKED、WAITING与TIMED_WAITING,对应不同阻塞场景。合并后的场景如下:

RUNNABLE与三种阻塞状态的转换

  1. RUNNABLE到BLOCK状态

只有一种情况会触发这种转换,就是线程等待synchronized时。syncchronized修改的方法、代码同一时刻只允许一个线程执行,当有线程占用时,其他线程只能等待。当等待线程获取到synchronized隐式锁时,会切回到RUNNABLE状态。

需要注意的一个问题是,调用阻塞API时,如IO,Java线程对象的状态不会是BLOCKED。虽然实际上OS会将此线程休眠,等待再次调度。但Java语言层面上感知不到,也不用感知。

      2. RUNNABLE到WAITING状态

有三种情况会让线程从RUNNABLE切换到WAITING状态:

  • 线程获取synchronized后,调用了无参数的Object.wait()方法;这种情况会在“等待-通知机制”一节中详细说明;

  • 线程执行时调用了其他线程的thread.join()方法,等待其他线程结束,再重新变为RUNABLE状态;

  • 调用lockSupport.park()方法,这是底层方法,我们不会用到,不介绍;

3. RUNNABLE到TIMED_WAITING状态

到TIMED_WAITING与WAITING类似,只是多了一个time参数。具体来说有四种情况:

  • 有超时参数的sleep,Thread.sleep(long);

  • 有超时参数的wait,Object.wait(long);

  • 有超时参数的join,Thread.join(long);

  • 有超时参数的LockSupport(为了完整正确,我们不会用到的);

基本的线程使用

线程的创建方法:Thread/Runnable

线程的创建有两种基本的方式:Thread对象与Runnable接口。使用Thread创建线程:

// 自定义线程对象
class MyThread extends Thread {
  public void run() {
    // 线程需要执行的代码
    ......
  }
}
// 创建线程对象
MyThread myThread = new MyThread();

使用Runnable创建线程:

// 实现 Runnable 接口
class Runner implements Runnable {
  @Override
  public void run() {
    // 线程需要执行的代码
    ......
  }
}
// 创建线程对象
Thread thread = new Thread(new Runner());

线程的启动方式

线程的启动只有一种方式,start方法:

MyThread myThread = new MyThread();
// 从 NEW 状态转换到 RUNNABLE 状态
myThread.start();

线程的让渡方式:sleep/join/yield

让渡线程一般有三种方式:

  • sleep:这是Thread的静态方法,一定要带超时参数,表示执行sleep的线程进入TIMED_WAITING状态;

  • yield:也是Thread的静态方法,表示暂时让渡CPU,从OS层面上看,线程从运行状态到可执行状态,重试等时间片;

  • join:Thread的对象方法,在threadA的run中调用threadB.join,表示threadA等threadB执行结束。

线程的终止方法:stop/interrupt

调用stop方法可以直接终止线程,但此方法已经被标记为@Deprecated,生产环境上也不能使用。因为stop方式过于粗暴。直接清除线程对象,没给线程任何清理现场的机会。如获取了一个锁,还没有来得及释放,就stop了。那这个锁就再也没有机会释放了。这在生产环境上是不可以接受的。

interrupt就温和很多,interrupt方法通知线程需要中止了。线程收到这个通知,可以选择清理现场,结束任务,也可以选择继续执行(但是不建议)。

线程获取interrupt的方式两种,接收InterruptedException异常与主动查看interrupt标志位。一些阻塞操作会抛出InterruptedException,如wait()、join()、sleep()等。同时线程还提供了isInterrupted()方法,当执行线程的interrupted方法后,此方法返回true。

线程的使用规范

  1. 尽量不要直接创建线程

    在实际的生产环境上,我们强烈建议不要直接使用线程,而要使用线程池的方式。有如下原因:

    • 线程的创建与销毁,有巨大的消耗,线程池可以避免此问题;

    • 线程是一种系统资源,使用线程池可以管控线程资源;

  2. 正确地启动线程,不要调用run

不要直接调用run方法,run只是一个方法,不会启动线程;

      3. 正确地终止线程,不要调用stop

不要使用stop对线程进行终止,线程的正确终止方法会在后面详细介绍;

      4. 不要使用yield方法做线程同步

yield是不可靠的,只是暂时让线程重新等待,下次什么时机执行是不可控制的。

总得来说,我们建议不要直接使用Thread的基础API,Java提供了更多高级的API、机制和工具实现并发的各种场景。线程基础API,是最后的选择。


点赞
收藏
回复
分享
举报
浏览1019 编辑于2020-07-31 07:13未知归属地
全部评论
最多点赞
最新发布
最早发布
写回答
新增插入模板功能
一键使用模板,快速填写内容,轻松发帖~
知道了
  • 为了保障您的信息安全,请勿上传您的敏感个人信息(如您的密码等信息)和您的敏感资产信息(如关键源代码、签名私钥、调试安装包、业务日志等信息),且您需自行承担由此产生的信息泄露等安全风险。
  • 如您发布的内容为转载内容,请注明内容来源。

我要发帖子

目录 收起
线程的状态
线程状态的切换
RUNNABLE与三种阻塞状态的转换
基本的线程使用
线程的创建方法:Thread/Runnable
线程的启动方式
线程的让渡方式:sleep/join/yield
线程的终止方法:stop/interrupt
线程的使用规范
了解社区公约,与您携手共创和谐专业的开发者社区。