首页 > 其他分享 >线程的生命周期

线程的生命周期

时间:2023-07-30 23:00:45浏览次数:27  
标签:生命周期 队列 阻塞 任务 线程 new 方法


线程的生命周期


1、线程的5个生命周期

  • 新建: 刚使用new方法创建出来的线程;
  • 就绪: 调用线程的start()方法后,线程处于等待CPU分配资源阶段,当线程获取到CPU资源后开始执行;
  • 运行: 当就绪的线程被调度并获得CPU资源时,便会进入运行状态,run()方法定义了线程的操作和功能;
  • 阻塞: 在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,如sleep()、wait()之后,线程就会处于阻塞状态。这个时候需要其他机制将处于阻塞状态的线程唤醒,如notify()、notifyAll()方法。被唤醒的线程不会立即执行run方法,会回到就绪阶段,再次等待CPU分配资源进入运行状态。
  • 销毁: 如果 线程正常执行完毕 或 被提前强制终止 或 出现异常导致结束,那么线程就会被销毁并释放资源。

2、线程的6种状态

  • 初始状态(NEW): 线程被创建出来但没有被调用 start()
  • 运行状态(RUNNABLE): 线程被调用了 start()等待运行的状态。
  • 阻塞状态(BLOCKED): 需要等待锁释放。
  • 等待状态(WAITING): 表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
  • 超时等待状态(TIME_WAITING): 可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
  • 终止状态(TERMINATED): 表示该线程已经运行完毕。

3、线程的创建

1、创建线程

创建线程三种方式:

  • 继承Thread
  • 实现Runnable
  • 实现Callable

1、继承Thread

public class CreateThreadDemo {

    public static void main(String[] args){
        new ThreadDemo().start();
    }

    public static class ThreadDemo extends Thread {
        @Override
        public void run() {
            System.out.println("ThreadDemo:" + Thread.currentThread());
        }
    }
}

缺点:Java单继承

2、实现Runnable

public class CreateThreadDemo {

    public static void main(String[] args){
        new Thread(new RunnableDemo()).start();
    }
    
    //不用担心单继承,没有返回值
    public static class RunnableDemo implements Runnable {

        @Override
        public void run() {
            System.out.println("RunnableDemo:" + Thread.currentThread());
        }
    }
}

特点:没有返回值,类似stream的foreach

3、实现Callable

public class CreateThreadDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new CallableDemo());
        new Thread(futureTask).start();
        //get()方法获取返回值
        System.out.println(futureTask.get());
    }

    //有返回值也可以抛出异常
    public static class CallableDemo implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("CallableDemo:" + Thread.currentThread());
            return "return CallableDemo:" + Thread.currentThread();
        }
    }
}

特点:有返回值,类似stream的map,在Callable< T >决定返回类型

get()方法会阻塞线程

区别:

  • Thread只能单继承,Runnable和Callable可以多实现
  • Thread和Runnable不能得到返回值,Callable可以获取返回值及捕获异常

2、创建线程池

1、使用线程池的好处

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2、通过构造方法实现

线程的生命周期_阻塞状态

/*
* corePoolSize 核心线程:核心线程数定义了最小可以同时运行的线程数量
* maximumPoolSize 最大线程:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数;
* keepAliveTime 空闲线程存活时间:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁
* unit 空闲线程存活时间单位:`keepAliveTime` 参数的时间单位
* workQueue 阻塞队列:类型linked,容量10000
* threadFactory 线程工程:默认
* handler 饱和策略:抛出 RejectedExecutionException来拒绝新任务的处理
*/
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);
/*
* 核心线程:5
* 最大线程:200
* 空闲线程存活时间:10
* 空闲线程存活时间单位:秒
* 阻塞队列:类型linked,容量10000
* 线程工程:默认
* 饱和策略:抛出 RejectedExecutionException来拒绝新任务的处理
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
                200,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

3.ThreadPoolExecutor7个参数

  1. 核心线程数 corePoolSize 核心线程数定义了最小可以同时运行的线程数量。
  2. 最大线程数 maximumPoolSize 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
  3. 阻塞队列 workQueue 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
  4. 存活时间 keepAliveTime 当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
  5. 存活时间单位 unit keepAliveTime 参数的时间单位。
  6. 线程工厂 threadFactory executor 创建新线程的时候会用到。
  7. 饱和策略 handler 如果当前同时运行的线程数量达到最大线程数量并且阻塞队列也已经被放满了任务时,会根据饱和策略来处理多余的任务。
    常见饱和策略:
  • ThreadPoolExecutor.AbortPolicy 抛出 RejectedExecutionException 异常来拒绝新任务的处理。
  • ThreadPoolExecutor.CallerRunsPolicy 调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
  • ThreadPoolExecutor.DiscardPolicy 不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy 此策略将丢弃最早的未处理的任务请求。

4.线程池运行流程

  1. 线程池创建,准备好 core 数量的核心线程,准备接受任务
  2. 新的任务进来,用 core 准备好的空闲线程执行。
  1. 核心线程 core 满了,就将再进来的任务放入阻塞队列中。空闲的 core 就会自己去阻塞队列获取任务执行
  2. 阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量
  3. 如果线程数开到了 max 的数量,还有新任务进来,就会使用 RejectedExecutionHandler 指定的拒绝策略拒绝任务
  4. max 都执行完成,有很多空闲。在指定 keepAliveTime 后,会释放 Max-core 数量空闲的线程。最终保持到 core 大小。new LinkedBlockingQueue<>()默认是integer的最大值,内存不够
  1. 所有的线程创建都是由指定的 factory 创建的

总结:核心线程 -> 阻塞队列 -> 新线程 -> 拒绝策略 -> 自动释放空闲核心线程

3、通过 Executor 框架的工具类 Executors 来实现

四种常见线程池:

  • CachedThreadPool
  • FixedThreadPool
  • ScheduledThreadPool
  • SingleThreadExecutor

1.CachedThreadPool

/**
 * 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,
 * 若无可回收,则新建线程。
 * 没有核心线程,所有线程都可以回收。
 */
Executors.newCachedThreadPool();

2.FixedThreadPool

/**
 * 创建一个定长线程池,可控制线程最大并发数,
 * 超出的线程会在队列中等待。
 * 核心线程数和最大线程数相同,固定线程数大小,所有线程都不可以回收。
 */
Executors.newFixedThreadPool(10);

3.ScheduledThreadPool

/**
 * 创建一个定长线程池,支持定时及周期性任务执行。
 * 可以指定多长时间以后执行任务。定时任务线程池。
 */
Executors.newScheduledThreadPool(10);

4.SingleThreadExecutor

/**
 * 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,
 * 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
 * 单线程的线程池,核心和最大线程数都为 1。
 * 后台从队列中取一个执行一个,相当于后台用单线程执行任务。
 */
Executors.newSingleThreadExecutor();

4、submit和execute的区别

  1. execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
  2. submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Futureget()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

4、线程的3种阻塞

  • 等待阻塞: 正在运行中的线程执行wait()方法时,JVM会把该线程放入等待队列中。
  • 同步阻塞: 运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用,则JVM会把该线程放入锁池中。
  • 其他阻塞: 运行的线程执行sleep()、join()方法,或者发出了IO请求时,JVM会把该线程置为阻塞状态。

5、线程的3种结束

  • 正常结束: run()或call()方法执行完成,线程正常结束;
  • 异常结束: 线程执行过程中抛出一个未捕获的异常导致结束;
  • 强制结束: 调用线程终止方法强制结束线程:

参考:

  • 使用退出标志
    定义一个volatile修饰的boolean型的标志位 ,在线程的run方法中根据这个标志位是为true还是为 false 来判断是否终止,这种情况多用于while循环中。
    (使用volatile目的是保证可见性,一处修改了标志,处处都要去主存读取新的值,而不是使用缓存)
  • Interrupt 方法
    使用 interrupt 方法中断线程有两种情况
  • 线程处于阻塞状态
    如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时, 会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。
try {
	threadDemo.start();
	//阻塞线程
	threadDemo.wait();
	//中断线程
	threadDemo.interrupt();
}catch (InterruptedException e){
	//抛出异常,强制跳出,线程中断
	e.printStackTrace();
}
  • 线程未处于阻塞状态
    使用 isInterrupted() 判断线程的中断标志来退出循环。当使用 interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
//中断线程
threadDemo.interrupt();
//通过while循环不断确认线程是否已经终止
while (threadDemo.isInterrupted()) {

}
System.out.println("线程中断");
  • stop 方法
    调用stop()方法,该方法不安全,容易导致死锁
  • 调用stop方法会立刻终止run()方法中剩余的全部任务,包括catch或finally中的任务,并且抛出ThreadDeath异常,因此可能会导致任务执行失败。
  • 调用stop方法会立刻释放改线程所持有的所有锁,导致数据无法完成同步,出现数据不一致的问题。

衍生应用: Java多线程异步任务


标签:生命周期,队列,阻塞,任务,线程,new,方法
From: https://blog.51cto.com/u_15402092/6903674

相关文章

  • 线程不安全函数学习
    转自:https://blog.csdn.net/qq_26499321/article/details/72085592,https://blog.icrystal.top/archives/13.html1、线程不安全线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现......
  • 关于Java的多线程实现
    多线程介绍进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以......
  • - 通过结合前端页面实现ORM对数据的增删改查 - Django中如何创建表关系 - 一对一
    通过结合前端页面实现ORM对数据的增删改查案例:写一个页面,就是把数据库中的数据以表格的形式展示出来,然后在每一行的后面加两个按钮,分别是修改、删除的按钮1.首先在数据库创建一个表格1.在model.py中创建表格 2.pythonmanage.pymakemigratins迁移记录   3.......
  • java使用线程池实现接口自动化中的并发测试
    importjava.util.concurrent.ExecutionException;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.Future;publicvoidtest()throwsExecutionException,InterruptedException{ExecutorServ......
  • java多线程
    1、什么是JUC官方文档+源码​ 面试高频问java.utiljava.util.concurrentjava.util.concurrent.atomicjava.util.concurrent.locks​ java,util工具包、包、分类业务:普通的线程代码ThreadRunnable没有返回值2、线程和进程线程和进程如果不能用一句话说出来的......
  • 5_Spring_Bean的生命周期
    5_Spring_Bean的生命周期bean从创建到销毁经历的各个阶段以及每个阶段所调用的方法1通过构造器创建bean实例     执行构造器2为bean属性赋值            执行set方法3初始化bean                调......
  • 【Java】《2小时搞定多线程》个人笔记
    简介基于慕课网站上的一个一元钱课程《2小时搞定多线程》的个人笔记。线程的起源我们先来看看网络中关于线程起源的说明,理解线程的来龙去脉对于掌握多线程有一定帮助。此部分内容整理自下面两篇网络博客:#线程是什么#线程的起源线程的起源与计算机的发展息息相关。早期的计算机系......
  • C#中跨线程更新UI简单方法
    .NET3.5中,C#winform无法直接在子线程中更新UI组件的属性,会报“更新UI的线程非UI组件的创建线程”的错误,需要用到委托更新。有两种方式:方式1:stringtest="测试...";this.BeginInvoke((Action)deleg......
  • 设备驱动-10.中断子系统-4.3中断线程化处理-threaded_irq
    1.threaded_irq引入工作队列用起来挺简单,但是它有一个缺点:工作队列中有多个work,前一个work没处理完会影响后面的work执行,导致后面的work没法快速响应。那么可以再内核自己创建一个线程来单独处理,不跟别的work凑在一块了。比如在Linux系统中,对于存储设备比如SD/TF卡,它......
  • window线程同步的四种方法
    原文链接:线程同步的四种方式线程同步的四种方法1、临界区(CriticalSection):通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。优点:保证在某一时刻只有一个线程能访问数据的简便办法缺点:虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来......