当线程被创建并启动以后,它并不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。
Java中的线程状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:
public class Thread {
public enum State {
/* 新建 */
NEW ,
/* 可运行状态 */
RUNNABLE ,
/* 阻塞状态 */
BLOCKED ,
/* 无限等待状态 */
WAITING ,
/* 计时等待 */
TIMED_WAITING ,
/* 终止 */
TERMINATED;
}
// 获取当前线程的状态
public State getState() {
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
}
通过源码我们可以看到Java中的线程存在6种状态,每种线程状态的含义如下
线程状态 | 具体含义 |
---|---|
NEW(新建) | 创建线程对象 |
RUNNABLE(就绪) | start 方法被调用,但是还没有抢到CPU执行权 |
BLOCKED(阻塞) | 线程开始运行,但是没有获取到锁对象 |
WAITING(等待) | wait 方法 |
TIMED_WAITING(计时等待) | sleep 方法 |
TERMINATED(结束状态) | 代码全部运行完毕 |
线程池就是一个可以复用线程的技术
系统创建一个线程的成本是比较高的
因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程
对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点"舍本逐末"了。
针对这一种情况,为了提高性能,我们就可以采用线程池。
线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。
等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中变成为空闲状态。
等待下一次任务的执行。
总结:将线程对象交给线程池维护, 可以降低系统成本, 从而提升程序的性能
JDK 对线程池也进行了相关的实现,我们可以使用Executors中所提供的静态方法来创建线程池
方法 | 介绍 |
---|---|
static ExecutorService newCachedThreadPool ( ) |
创建一个默认的线程池 |
static newFixedThreadPool ( int nThreads ) |
创建一个指定最多线程数量的线程池 |
构造方法 | 描述 |
---|---|
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue |
用给定的初始参数创建一个新的 ThreadPoolExecutor |
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, //核心线程数量
5, //最大线程数量
60, //空闲线程最大存活时间
TimeUnit.SECONDS, //时间单位
new ArrayBlockingQueue<>(10), //任务队列:最多允许多少个线程任务去排队
Executors.defaultThreadFactory(), //创建线程工厂:线程对象的创建, 交给工厂来完成, 不再是自己创建了
new ThreadPoolExecutor.AbortPolicy() //任务的拒绝策略
);
for (int i = 1; i <= 15; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"hgfcdxz");
}
});
pool.shutdown();
}
}
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程
这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;
另一方面线程的细节管理交给线程池处理,优化了资源的开销。
而线程池不允许使用Executors去创建,而要通过 ThreadPoolExecutor 方式
RejectedExecutionHandler 是 jdk 提供的一个任务拒绝策略接口,它下面存在4个子类。
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法, 绕过线程池直接执行。
注:明确线程池最多可执行的任务数 = 队列容量 + 最大线程数
新任务提交时发现核心线程都在忙 ( 任务队列也满了 )
此时创建新的线程
提交的线程任务数量 > 核心线程数 + 任务队列的数量
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝
提交的线程任务数量 > 最大线程数量 + 任务队列的数量
提交线程任务的方式 :
方法名 |
---|
void execute(Runnable command) |
Future submit(Callable task) |
推荐使用 submit , 因为能够同时接收 Runnable 和 Callable
面试题
为什么禁止使用Executors创建线程池?
从newFixedThreadPool的底层来看,LinkedBlockingQueue是一个用链表实现的有界阻塞队列,它并未指定容量,是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE.对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况就有可能因为任务过多导致内存溢出问题。
如何正确创建线程池?
避免使用Executors创建线程池,主要是避免使用其中的默认实现。我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池,在创建的同时,给BlockQueue指定容量就可以。