java线程生命周期(thread)

Posted xinyuan_java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java线程生命周期(thread)相关的知识,希望对你有一定的参考价值。

Java 线程生命周期

线程生命周期总览

这是线程生命周期的总览图:

在图中可以看到一个线程的所有状态,我们先了解一下这些状态的含义。

NEW - 初始状态,一个新创建的线程,还没开始执行。

RUNNABLE - 可执行的状态,要么是在执行,要么是一切就绪等待执行,例如等待分配CPU时间。

WAITING - 等待状态,等待其他的线程去执行特定的动作,没有时间限制。

TIMED_WAITING - 限时等待状态,等待其他的线程去执行特定的动作,这个是在一个指定的时间范围内。

BLOCKED - 阻塞状态,等待锁,以便进入同步块儿。

TERMINATED - 终止状态,线程执行结束。

如上图,这些状态大体上可以分为 2 个部分:

(1)正常过程

创建线程(NEW),然后线程做自己的工作(RUNNABLE),做完之后就终止了(TERMINATED)。

(2)非正常情况

线程执行的时候遇到了点问题,需要等待(WAITING、TIMED WAITING)一会儿,或者被别人阻塞了(BLOCKED)。

线程结束等待之后,也可能进入阻塞状态。

非正常情况结束之后,线程再继续执行自己的工作,所以箭头也好理解了。

NEW

NEW 线程是被创建出来后还没执行的。

这个状态直到调用了 start() 方法后变化。

下面的代码显示了一个 NEW 状态的线程:

Thread t =newMyThread();

复制

RUNNABLE

我们创建一个线程,然后调用了 start() 方法,那么这个线程就从 NEW 变为了 RUNNABLE 状态。

处于 RUNNABLE 状态的线程可能正在运行,也可能在等待系统为其分配资源。

JVM 中的 Thread-Scheduler(线程调度器)会为每个线程分配一个固定的执行时间,所以一个线程一次就只执行一段时间,时间到了之后,就会让其他的 RUNNABLE 状态的线程执行。

下面的代码显示了 RUNNABLE 状态:

Thread t =newMyThread();
t.start();

复制

WAITING

一个线程在等待其他线程执行特定动作的时候就处于 WAITING 状态。

调用下列方法后就会进入 WAITING 状态:

  1. object.wait()

  1. thread.join()

  1. LockSupport.park()

例如:

我们创建并启动 1个线程 threadA

threadA 又创建了 threadB,然后启动

threadA 里面调用了 threadB.join(),那么这时 threadA 就进入了 WAITING 状态,直到 threadB 执行结束。

TIMED WAITING

WAITING 一样也是等待状态,不同是 WAITING 没有时间限制,而 TIMED_WAITING 有等待时间限制。

通过下面的方法可以进入 TIMED_WAITING 状态:

  1. thread.sleep(long millis)

  1. wait(int timeout) or wait(int timeout, int nanos)

  1. thread.join(long millis*)*

  1. LockSupport.parkNanos

  1. LockSupport.parkUntil

BLOCKED

一个线程要执行的代码被其他线程锁了,所以在阻塞在这儿了,需要等待人家开锁后才能执行。

例如:

我们创建 2 个线程 threadA、threadB

有一个同步方法 A(),意味着一次只能有一个线程访问

这2个线程都要执行 A()threadA 先进去执行了,那么 threadB 就进不去了,处于 BLOCKED 状态。

TERMINATED

这是线程生命结束的状态,一个线程的结束,可能是正常执行完成了,也可能是异常终止了。

小结

最终线程的生命周期图如下:

梳理一下整体的思路:

(1)正常情况

创建线程(NEW)=> 线程工作(RUNNABLE)=> 线程终止(TERMINATED)

(2)非正常情况

线程执行时需要和其他线程配合执行,需要等待(WAITING、TIMED WAITING)

线程执行时还可能与其他线程竞争,被阻塞(BLOCKED)

非正常情况结束后,就要回到 RUNNABLE 状态,继续执行。

创建线程的三种方式的对比

一、线程的三种创建方式

1.继承Thread类方式

创建一个类继承Thread类,重写run()方法,在主线程中创建此类的对象,调用对象的start()方法,可启动线程

public class Demo4

public static void main(String[] args)

MyThread mt = new MyThread();

mt.start();

static class MyThread extends Thread

@Override

public void run()

System.out.println("aaaaa");

2.实现Runnable接口

创建一个类实现Runnable接口,在主线程中创建这个类的对象实例,新创建一个线程并调用start()方法启动线程,需要先将实现Runnable接口的类的对象当做参数传给这个新的线程,这样就实现了线程的启动。

public class Demo5

public static void main(String[] args)

newThread thread = new newThread();

new Thread(thread).start();

static class newThread implements Runnable

@Override

public void run()

System.out.println("bbbb");

3.实现Callable接口

可以和使用实现Runnable接口方式创建线程方式比较,就是相当于多包装了一层,原来将Runnable对象直接丢到new Thread()中,也就是传入目标对象后调用start()方法启动线程,现在是不仅要创建实现Callable接口的对象,还需要util包下的concurrent下的FutureTask,创建FutureTask对象时需要传入实现Callable接口的对象到构造方法中,将FutureTask对象作为Thread的目标对象启动线程。

public class Demo6

public static void main(String[] args)

callableThread ct = new callableThread();

FutureTask task = new FutureTask(ct);

new Thread(task).start();

static class callableThread implements Callable

@Override

public Object call() throws Exception

System.out.println(Thread.currentThread().getName());

return 2;

二、三种创建方式优缺点

1.使用实现Runnable接口比使用继承Thread具有的优势

通过创建任务,然后给线程分配的方式实现多线程,更适合多个线程同时执行相同的任务的情况;可以避免单继承所带来的局限性;任务与线程本身是分离的,提高了程序的健壮性;线程池接受Runnable类型,不接受Thread类型。

2.使用Thread类

优点:编写相对简单,访问当前线程直接使用this就可以获得当前线程。

缺点:由于线程类已经继承了Thread类,所以不能再继承其他的父类。

3.使用Runnable接口

优点:没有继承Thread类,所以可以继承其他的类,适合多线程访问同一资源的情况,将cpu和数据分开,形成清晰的模型,较好的体现了面向对象的思想

缺点:编程相对复杂,要想获得当前线程对象,需要使用Thread.currentThread()方法。

3.使用callable接口

优点:也可以继承其他的类,多线程可以共享同一个target对象,适合多线程访问同一资源的情况,将cpu和数据分开,形成清晰的模型,较好的体现了面向对象的思想,还有返回值

缺点:编程稍显复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

Java中父子线程的生死联系

Java中父子生死,有时息息相关,但有时却又没有关系,我们分为三种情况进行演示:

  • 父死-子不死

  • 父死-子瞬间死

  • 父等待子死后,继续执行。

父死-子不死

最常见的情况,主线程中开启了一个子线程,开启之后,主线程与子线程互不影响各自的生命周期,即主线程结束,子线程还可以继续执行;子线程介素,主线程也能继续执行。

public static void main(String[] args)

System.out.println("主线程开始");

Thread sonthread=new Thread(()->

System.out.println("子线程开始");

while (true)

try

Thread.sleep(2000);

catch (InterruptedException e)

e.printStackTrace();

System.out.println("我是子线程,我还活着");

);

sonthread.start();

System.out.println("主线程结束");

父死-子瞬间死

子线程设置为父线程的守护线程,主线程开启了子线程,但是主线程结束,子线程也随之结束。

public static void main(String[] args)

System.out.println("主线程开始");

Thread sonthread=new Thread(()->

System.out.println("子线程开始");

while (true)

System.out.println("我是子线程,我还活着");

);

sonthread.setDaemon(true);

sonthread.start();

System.out.println("主线程结束");

父等待子死后,继续执行

主线程开启了一个子线程,主线程必须要等子线程运行完之后,才能结束主线程

public static void main(String[] args) throws InterruptedException

System.out.println("父线程开始");

Thread sonthread=new Thread(()->

System.out.println("子线程开始");

try

Thread.sleep(3000);

catch (InterruptedException e)

e.printStackTrace();

System.out.println("子线程死了");

);

sonthread.start();

sonthread.join();

System.out.println("父线程死了");

注意:这里使用了join()方法,让主线程等待子线程结束,然后主线程继续执行。这里join()方法必须要在子线程启动之后,再调用。

进程是资源分配的基本单位,线程是cpu调度的基本单位。对于cpu来说,其实不存在主线程和子线程之分,都是一个线程。进程的资源是进程下面的线程所共享的,只要进程还在,线程就可以正常执行,也就是说线程是依赖于进程的,线程与线程之间并不存在依赖关系,一个线程的死亡理论上不会对其他线程造成影响。但是上面通过调用JVM提供的接口,例如setDaemon与join改变了主线程与子线程的关系,这些应该是JVM接口代码做了处理干扰了线程的生命周期。

守护线程与非守护线程本质上没什么区别,但是如果虚拟机中存活的线程都是守护线程的时候,虚拟机就会退出,只要虚拟机中还有一个非守护线程,虚拟机就不会退出。

以上是关于java线程生命周期(thread)的主要内容,如果未能解决你的问题,请参考以下文章

Java线程生命周期

Java 并发:学习Thread 类

JAVA学习笔记线程的生命周期及Thread类常用方法

Java线程生命周期及状态Java线程安全的的理解Thread与RunnableJava守护线程

java基础——线程的常用方法和线程生命周期

JAVA线程生命周期