1.双亲委派模型
面试官:请你简单谈谈双亲委派模型?
求职者:
双亲委派模型也可以叫做双亲委派机制;
当一个类加载器收到加载一个类的请求的时候,该加载器不会直接去加载这个类,而是将这个请求委托到自己的父加载器尝试加载,层层的委托一直到最高一层的加载器:启动类加载器;当启动类加载器也无法加载的时候,才会层层向下传递,让子类加载器尝试进行类的加载。
具体如下图所示:
双亲委派整个过程分为以下几步:
1.假设用户刚刚摸鱼写的Test类想进行加载,这个时候首先会发送给应用程序类加载器ApplicaiotnCloassLoader;
2.然后ApplicationClassLoader并不会直接去加载Test类,而是会委派于父类加载器完成此操作,也就是ExtClassLoader;
3.ExtensionClassLoader同样也不会直接去加载Test类,而是会继续委派于父类加载器完成,也就是BootstrapClassLoader;
4.BootstrapClassLoader这个时候已经到顶层了,没有父类加载器了,所以BootstrapClassLoader会在jdk/lib目录下去搜索是否存在,因为这里是用户自己写的Test类,是不会存在于jdk下的,所以这个时候会给子类加载器一个反馈。
5.ExtensionClassLoader收到父类加载器发送的反馈,知道了父类加载器并没有找到对应的类,爸爸靠不住,就只能自己来加载了,结果显而易见,自己也不行,没办法,只能给更下面的子类加载器了。
6.ApplicationClassLoader收到父类加载器的反馈,顿时明白,原来爸爸虽然是爸爸,但是他终究不能管儿子的私事,所以这时候,ApplicationClassLoader就自己尝试去加载。
结果,就这样成功了,走了一大圈,兜兜转转还是自己干。
2.Thread、Runable的区别
面试官:相比你一定接触过多线程问题,那么请你简单谈谈Thread与Runable的区别?
求职者:
Thread和Runable的实质是继承关系,没有可比性。无论使用Runable还是Thread,都会new Thread ,然后执行run方法。用法上如果由复杂的线程操作要求,那就继承Thread,如果只是简单的执行一个任务,那就实现Runable。其实二者主要是使用上的不同。
具体请看下面的代码示例:
继承Thread:
public class Test {
//卖票问题
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
static class MyThread extends Thread{
private int ticket=5;
@Override
public void run() {
while(true){
System.out.println("Thread Ticket ="+ticket--);
if(ticket<0){
break;
}
}
}
}
}
实现Runnable接口:
public class Test {
//卖票问题
public static void main(String[] args) {
MyThread myThread=new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
}
static class MyThread implements Runnable{
private int ticket=5;
@Override
public void run() {
while(true){
System.out.println("Thread Ticket ="+ticket--);
if(ticket<0){
break;
}
}
}
}
}
3.线程池的底层工作原理
面试官:请你谈谈你对线程池的底层工作原理的理解?
求职者:
线程池中一共有7个参数,即corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler:
corePoolSize 表示线程池的核心线程数。当有任务提交到线程池时,如果线程池中的线程数小于corePoolSize,那么则直接创建新的线程来执行任务。
workQueue任务队列,它是一个阻塞队列,用于存储来不及执行的任务的队列。当有任务提交到线程池的时候,如果线程池中的线程数大于等于corePoolSize,那么这个任务则会先被放到这个队列中,等待执行。
maximumPoolSize表示线程池支持的最大线程数量。当一个任务提交到线程池时,线程池中的线程数大于corePoolSize,并且workQueue已满,那么则会创建新的线程执行任务,但是线程数要小于等于maximumPoolSize。
keepAliveTime表示非核心线程空闲时保持存活的时间。非核心线程即workQueue满了之后,再提交任务时创建的线程,因为这些线程不是核心线程,所以它空闲时间超过keepAliveTime后则会被回收。
unit 表示非核心线程空闲时保持存活的时间的单位
threadFactory 表示创建线程的工厂,可以在这里统一处理创建线程的属性
handler表示拒绝策略,当线程池中的线程达到maximumPoolSize线程数后且workQueue已满的情况下,再向线程池提交任务则执行对应的拒绝策略
具体的线程池执行过程步骤如下:
当用户向线程池提交线程任务时,当线程池中的线程数小于核心线程数时,那么不管线程池中是否有空闲的线程,都会创建一个新的线程来执行任务;
当用户向线程池提交的线程数达到了核心线程数,且此时没有空闲的核心线程数,则此时会将多余的线程放入workQueue中;
当用户向线程池提交的线程数达到了核心线程数且workQueue已满,此时线程池会创建非核心线程来执行线程任务,但总线程数应该小于maximumPoolSize;
当用户向线程池提交线程任务时,若线程池中的线程数达到了maximumPoolSize,且workQueue已满,此时会执行拒绝策略来拒绝接受任务。
具体执行步骤如下图所示: