大厂面试问答题汇总分析--- 多线程问题

本文详细分析了Java线程池的工作原理,包括不同类型的线程池及其特点,线程池的作用,如减少资源开销、提高响应速度和线程管理。此外,还探讨了ThreadPoolExecutor类的属性、构造方法,以及线程池的拒绝策略。同时,文章还对比了线程和进程的区别,并介绍了多线程的实现方式。
摘要由CSDN通过智能技术生成

大厂面试---Redis面试题(含答案,相关知识点,面试考察次数统计)_安吉_lh1029的博客-CSDN博客

大厂面试问答题汇总分析---数据库(索引-聚集/非聚集,事务,mySql, 锁)_安吉_lh1029的博客-CSDN博客

大厂面试问答题汇总分析---网络安全类问题_安吉_lh1029的博客-CSDN博客

大厂面试问答题汇总分析--- 秒杀 / 限流 / 高并发_安吉_lh1029的博客-CSDN博客

【锁】的相关概念汇总_安吉_lh1029的博客-CSDN博客

大厂面试问答题汇总分析--- 多线程问题_安吉_lh1029的博客-CSDN博客

多线程具体应用案例可参考这篇:

使用多线程--交替打印数字问题 & 死锁代码实现案例_安吉_lh1029的博客-CSDN博客

1、线程池的工作原理 ,Java 并发类库提供的线程池有哪几种? 分别有什么特点?

1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作
线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这
个工作队列里。如果工作队列满了,则进入下个流程。
3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程
来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

 2、线程池的作用

减少资源的开销 可以减少每次创建销毁线程的开销提高响应速度 由于线程已经创建成功提高线程的可管理性。

线程池通过线程复用机制,并对线程进行统一管理,具有以下优点:

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

3 ThreadPoolExecutor类的属性

    // 线程池的控制状态,用高3位来表示线程池的运行状态,低29位来表示线程池中工作线程的数量
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //值为29,用来表示偏移量
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //线程池的最大容量,其值的二进制为:00011111111111111111111111111111(29个1)
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // 线程池的运行状态,总共有5个状态,用高3位来表示
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    //任务缓存队列,用来存放等待执行的任务
    private final BlockingQueue<Runnable> workQueue;

    //全局锁,对线程池状态等属性修改时需要使用这个锁
    private final ReentrantLock mainLock = new ReentrantLock();

    //线程池中工作线程的集合,访问和修改需要持有全局锁
    private final HashSet<Worker> workers = new HashSet<Worker>();

    // 终止条件
    private final Condition termination = mainLock.newCondition();

    //线程池中曾经出现过的最大线程数
    private int largestPoolSize;
    
    //已完成任务的数量
    private long completedTaskCount;

    //线程工厂
    private volatile ThreadFactory threadFactory;

    //任务拒绝策略
    private volatile RejectedExecutionHandler handler;

    //线程存活时间
    private volatile long keepAliveTime;

    //是否允许核心线程超时
    private volatile boolean allowCoreThreadTimeOut;

    //核心池大小,若allowCoreThreadTimeOut被设置,核心线程全部空闲超时被回收的情况下会为0
    private volatile int corePoolSize;

    //最大池大小,不得超过CAPACITY  
    private volatile int maximumPoolSize;

    //默认的任务拒绝策略
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

    private static final RuntimePermission shutdownPerm =
        new RuntimePermission("modifyThread");

    private final AccessControlContext acc;

通过上面的源码可知,线程池的运行状态总共有5种,其值和含义分别如下:

  • RUNNING: 高3位为111,接受新任务并处理阻塞队列中的任务
  • SHUTDOWN: 高3位为000,不接受新任务但会处理阻塞队列中的任务
  • STOP:高3位为001,不会接受新任务,也不会处理阻塞队列中的任务,并且中断正在运行的任务
  • TIDYING: 高3位为010,所有任务都已终止,工作线程数量为0,线程池将转化到TIDYING状态,即将要执行terminated()钩子方法
  • TERMINATED: 高3位为011,terminated()方法已经执行结束

4、ThreadPoolExecutor类的构造方法

通常情况下,我们使用线程池的方式就是new一个ThreadPoolExecutor对象来生成一个线程池。接下来,先看ThreadPoolExecutor类的构造函数:

    //间接调用最后一个构造函数,采用默认的任务拒绝策略AbortPolicy和默认的线程工厂
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue);
    //间接调用最后一个构造函数,采用默认的任务拒绝策略AbortPolicy
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory); 
    //间接调用最后一个构造函数,采用默认的默认的线程工厂
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler);
    //前面三个分别调用了最后一个,主要的构造函数
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);

接下来,看下最后一个构造函数的具体实现:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        //参数合法性校验
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        //参数合法性校验
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        //初始化对应的属性
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

下面解释下一下构造器中各个参数的含义:

  1. corePoolSize
    线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。

  2. maximumPoolSize
    线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize

  3. keepAliveTime
    线程空闲时的存活时间。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

4.unit
keepAliveTime参数的时间单位。

5.workQueue
任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的任务就会被保存到任务缓存队列中,等待被执行。
一般来说,这里的BlockingQueue有以下三种选择:

  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。因此,如果线程池中始终没有空闲线程(任务提交的平均速度快于被处理的速度),可能出现无限制的线程增长。
  • LinkedBlockingQueue:基于链表结构的阻塞队列,如果不设置初始化容量,其容量为Integer.MAX_VALUE,即为无界队列。因此,如果线程池中线程数达到了corePoolSize,且始终没有空闲线程(任务提交的平均速度快于被处理的速度),任务缓存队列可能出现无限制的增长。
  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。

6.threadFactory
线程工厂,创建新线程时使用的线程工厂。

7.handler
任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:

  • AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认策略;
  • CallerRunsPolicy:由调用execute方法的线程执行该任务;
  • DiscardPolicy:丢弃任务,但是不抛出异常;
  • DiscardOldestPolicy:丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)。
    当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

操作系统中进程调度策略有哪几种?

FCFS(先来先服务,队列实现,非抢占的)
SJF(最短作业优先调度算法)
优先级调度算法(可以是抢占的,也可以是非抢占的)
时间片轮转调度算法(可抢占的)
多级队列调度算法
多级反馈队列调度算法。

1、线程和进程的区别?

根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
关系:一个程序至少一个进程,一个进程至少一个线程。

2、多线程的具体实现?

四种方法:

方法一:继承Thread类,重写run()方法;

public class Thread1 extends Thread {

@Override

public void run( ){

}

}

方法二:实现Runnable接口,实现run()方法

public class Thread2 implements Runnable{

@Override

public void run( ){

}

}

3、实现Callable接口,实现call()方法;

public class Thread3 implements Callable<String>{

@Override

}

4、通过线程池创建线程

本文引用参考文章:

https://www.jianshu.com/p/704a6c5d337c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值