21java-多线程-高级

线程状态

​ 当线程被创建并启动以后,它并不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。

21java-多线程-高级_第1张图片

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
  • 构造方法
构造方法 描述
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 用给定的初始参数创建一个新的 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指定容量就可以。

你可能感兴趣的:(java,开发语言)