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 状态:
object.wait()
thread.join()
LockSupport.park()
例如:
我们创建并启动 1个线程 threadA
threadA 又创建了 threadB,然后启动
在 threadA 里面调用了 threadB.join(),那么这时 threadA 就进入了 WAITING 状态,直到 threadB 执行结束。
TIMED WAITING
和 WAITING 一样也是等待状态,不同是 WAITING 没有时间限制,而 TIMED_WAITING 有等待时间限制。
通过下面的方法可以进入 TIMED_WAITING 状态:
thread.sleep(long millis)
wait(int timeout) or wait(int timeout, int nanos)
thread.join(long millis*)*
LockSupport.parkNanos
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)的主要内容,如果未能解决你的问题,请参考以下文章