Java线程1

不读书的人,思想就会停止 

0. 思维导图

线程池是很多编程语言中都有的,虽然我现在很少写 java 了,但是对于面试或其他语言,其实都是相通的。这篇是聊下线程的基本内容,下面是基本的思维导图。Java线程1_第1张图片

1. 为什么需要线程池

任何程序,都是一些线程,执行一些任务组成的。任务会很多,如果我们每个任务都创建一个线程去执行。那么随着任务的增加,创建的线程数激增,会有很多问题:

  1. 系统可以创建的线程数不是无限的,因为线程需要占用内存等资源信息。

  2. 不是线程越多越好,线程越多,cpu 的核数是固定的,那么多个线程切换的时候,就会发生上下文切换影响性能。

  3. 线程创建和销毁本身也有开销,可能线程创建和销毁的时间比执行任务的时间还长,得不偿失。

public class OneTask { 
 
    public static void main(String[] args) { 
        Thread thread = new Thread(new Task());
        thread.start();
    } 
 
    static class Task implements Runnable { 
        @Override
        public void run() { 
           System.out.println("Thread Name: " + Thread.currentThread().getName());
        } 
    } 
}

如果只有一个线程执行任务那,又存在着 cpu 利用不充分的问题,比如我们很多任务可能需要 IO,在等待 IO 的时候,线程 sleep 会让出 cpu,从而不利于程序整体性能提升。

一个任务一个线程,系统执行慢;每个任务一个线程,又导致线程过多,导致一系列上述问题。

2. 线程池

为了让系统执行的足够快,又不想创建过多线程,占用过多资源,线程池就应运而生。线程池有以下好处:

  1. 线程池,故名思意,池子里面创建了一定的线程数,任务来了之后,直接运行,提升响应速度。

  2. 线程池有不少线程是长期存在的,那就不需要过多的线程创建和销毁的开销。

  3. 线程池统筹 cpu 和内存的使用,线程不够的时候创建,线程空闲下来的时候可以进行销毁,在系统性能和资源占用中保持一个平衡。

3. Java 线程池参数说明

这些也是面试中常问的问题,Java 中线程池的参数说明如下:

  • corePoolSize 核心线程数

  • maxPoolSize 最大线程数

  • keepAliveTime+ 时间单位 空闲线程的存活时间

  • ThreadFactory 线程工厂,用来创建线程。

  • workQueue 用来存放任务的队列

  • Handler 处理拒绝任务接口实现 举例:

3.1 线程池中线程数

Java 中的线程数开始的时候是 0 ,当添加任务的时候,首先查看线程池中线程数是否达到了核心线程数(corePoolSize),没有则创建;如果达到了核心线程数,则看下队列是否满了,如果队列也满了,则判断限制线程数是否达到了最大线程数(maxPoolSize),如果没达到,则继续创建线程;如果达到了,则按照 Handler 处理拒绝任务的方法来处理。

Java线程1_第2张图片 线程池

从这个图需要注意的:

  1. 线程池初始的状态下是没有线程的,直到来了一个任务。

  2. 非核心线程数的创建是在队列满了之后再创建,如果使用无界队列,比如LinkedBlockingQueue则队列永远不会满,也就不会创建非核心线程,线程池的最大线程数只能为 corePoolSize 。

3.2 keepAliveTime

keepAliveTime 和时间单位决定了非核心线程的存活时间,如果线程池的线程超过核心线程数,会根据这个时间来销毁非核心线程数,从而减少空闲时候的资源占用;任务多的话,又会根据上图规则来继续创建非核心线程。具备线程数弹性变化的能力。

3.3 ThreadFactory

创建线程的线程工厂,我们如果需要定制线程,比如设置线程的名称等,可以自定义线程工厂来替换默认的线程工厂来创建线程。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

3.4 workQueue

Java 线程池中的任务队列,常用的几种:

LinkedBlockingQueue     

没有容量限制的队列,也真是因为无限,则有可能造成 OOM 问题。

SynchronousQueue

这个队列很奇怪,没有空间存放数据,来了任务直接交给线程,不进行存放,性能好。也许会奇怪,为啥要有这样队列,我觉得解耦是一方面,解耦生产者和消费者,比如 solr 的客户端我记得在 6.3 版本的时候就是用这种队列来提交任务的。

DelayedWorkQueue       

延迟任务队列,当我们做任务定时调度的时候需要,使用此队列。采用堆这种数据结构,可以保持先执行的任务放在队头。这个队列也是无界队列,也容易引起 OOM 问题。

四 线程拒绝任务

线程在两种情况下会拒绝任务:

  1. 线程池已经调用 shutdown 了,相当于已经关闭了线程池,当然再来的任务会被拒绝掉。

  2. 按照刚才线程池中线程的创建过程描述,当线程已经无法再创建非核心线程的时候,再来的任务就会被拒绝掉。

拒绝又分几种拒绝策略:

  1. AbortPolicy:拒绝抛异常RejectedExecutionException这是线程池默认的拒绝策略,如果需要拒绝,则抛出运行时异常:RejectedExecutionException 调用者自行决定如何处理。

  2. DiscardPolicy:这个比较狠,直接默默地抛弃,不通知,容易丢失任务。

  3. DiscardOldestPolicy: 这个是删除在队列中存活最久的任务,腾出空间给新来的任务,也是默默地抛弃,无提示。

  4. CallerRunsPolicy: 这个是我觉得比较好的策略,它不拒绝,如果满足拒绝条件后,哪个线程执行提交任务,就在哪个线程执行这个任务。你不是提交快慢,那我就交给你执行,这样可以防止提交太快导致无法执行的问题,线程执行了任务就会减慢了提交任务的速度,给线程池执行腾出了时间。

你可能感兴趣的:(Java线程1)