并发编程-Java线程的生命周期

一、线程的状态

Java线程从创建到销毁,可能会经历一下6个状态:

状态名称说明
NEW初始状态,线程被构建,但是还没有调用start方法
RUNNABLED运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为”运行中”
BLOCKED阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使 用权,阻塞也分为几种情况
WAITING等待状态
TIMED_WAITING超时等待状态,超时以后自动返回
TERMINATED终止状态,表示当前线程执行完毕

如下图
在这里插入图片描述

二、线程状态操作

import java.util.concurrent.TimeUnit;

public class ThreadStatusDemo {

    public static void main(String[] args) {
        // TIMED_WAITING
        new Thread(()->{
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Time_Wating_Demo").start();
        // WAITING
        new Thread(()->{
            while (true) {
                synchronized (ThreadStatusDemo.class) {
                    try {
                        ThreadStatusDemo.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "Wating").start();
        new Thread(new BlockedDemo(), "Blocked-Demo-01").start();
        new Thread(new BlockedDemo(), "Blocked-Demo-02").start();
    }

    static class BlockedDemo extends Thread {
        @Override
        public void run() {
            synchronized (BlockedDemo.class) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行ThreadStatusDemo.main后,如果在Terminal下执行jps命令

三、生命周期阶段体现

new(初始化状态)

新建一个线程对象
例如下面代码,new了一个MyThread的对象t1,那么t1线程就是初始化状态。

 // 自定义一个线程类
class MyThread extends Thread {
  public void run() {
    // 省略业务代码
    ......
  }
}
// 创建线程对象
MyThread t1= new MyThread();

runnable(可运行(就绪)/ 运行状态)

在java线程层面上,runnable状态包括可运行(也叫就绪)状态和运行状态。

  1. 可运行转态(runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  2. 运行状态(Running):就绪状态的线程获得CPU并执行程序代码。

blocked(阻塞状态)

runnable状态到blocked状态,只有一种场景:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中,线程进入阻塞状态。阻塞的线程放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。


waiting(无时限等待)

有三种场景会导致线程从runnable到waiting

第一种场景,获得 synchronized 隐式锁的线程,调用无参数的 Object.wait() 方法,调用该方法的线程进入waiting状态。当其他的线程获得对应对象锁之后,调用notify或notifyall方法,waiting的线程才转换到runnable状态。

notify和notifyAll的区别:

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。

sleep和wait的区别:

  • 当线程执行sleep方法时,不会释放当前的锁(如果当前线程进入了同步锁),也不会让出CPU。sleep(milliseconds)可以用指定时间使它自动唤醒过来,如果时间不到只能调用interrupt方法强行打断。
  • 当线程执行wait方法时,会释放当前的锁,然后让出CPU,进入等待状态。只有当notify/notifyAll被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或是中途遇到wait() ,再次释放锁。
  • Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争
  • notify/notifyAll的执行只是唤醒沉睡的线程,而不会立即释放锁,必须执行完notify()方法所在的synchronized代码块后才释放。所以在编程中,尽量在使用了notify/notifyAll()后立即退出临界区。

第二种场景,调用无参数的 Thread.join() 方法。其中的 join() 是一种线程同步方法,例如有一个线程对象 thread A,当调用 A.join() 的时候,执行这条语句的线程会等待 thread A 执行完,而等待中的这个线程,其状态会从 runnable转换到 waiting。当线程 thread A 执行完,原来等待它的线程又会从 waiting状态转换到 runnable。

第三种场景,调用 LockSupport.park() 方法。Java 并发包中的锁,都是基于LockSupport实现的。调用 > > LockSupport.park() 方法,当前线程会阻塞,线程的状态会从 runnable转换到 WAITING。调用 LockSupport.unpark(Thread thread) 可唤醒目标线程,目标线程的状态又会从 waiting状态转换到 runnable。

park()与unpark():

concurrent包是基于AQS(AbstractQueuedSynchronizer)框架的,AQS框架借助于两个类:

  • Unsafe(提供CAS操作)
  • LockSupport(提供park/unpark操作)

LockSupport.park()和LockSupport.unpark(Thread thread)调用的是Unsafe中的native代码。

park与unpark的特点

park/unpark的设计原理核心是“许可”(permit):park是等待一个许可,unpark是为某线程提供一个许可。permit不能叠加,也就是说permit的个数要么是0,要么是1。也就是不管连续调用多少次unpark,permit也是1个。线程调用一次park就会消耗掉permit,再一次调用park又会阻塞住。如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。

unpark可以先于park调用。在使用park和unpark的时候可以不用担心park的时序问题造成死锁。相比之下,wait/notify存在时序问题,wait必须在notify调用之前调用,否则虽然另一个线程调用了notify,但是由于在wait之前调用了,wait感知不到,就造成wait永远在阻塞。

park和unpark调用的时候不需要获取同步锁。

park与unpark的优点

与Object类的wait/notify机制相比,park/unpark有两个优点:

  1. 以thread为操作对象更符合阻塞线程的直观定义。
  2. 操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。

底层实现原理

在Linux系统下,是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的。
mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。


timed_waiting(有时限等待)

有五种场景会触发这种转换:

  • 调用带超时参数的 Thread.sleep(long millis) 方法;
  • 获得 synchronized 隐式锁的线程,调用带超时参数的 Object.wait(long timeout) 方法;
  • 调用带超时参数的 Thread.join(long millis) 方法;
  • 调用带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
  • 调用带超时参数的 LockSupport.parkUntil(long deadline) 方法。

terminated(终止状态)

线程执行完 run() 方法后或者执行run() 方法的时候异常抛出,会转换到 terminated状态。
有时候我们需要强制中断 run() 方法的执行,例如 run() 方法访问一个很慢的网络,我们等不下去了,想终止怎么办呢?Java 的 Thread 类里面倒是有个stop()方法,不过已经标记为@Deprecated,所以不建议使用了。正确的姿势其实是调用**interrupt()**方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值