大厂面试---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;
}
下面解释下一下构造器中各个参数的含义:
-
corePoolSize
线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。 -
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize -
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