1.创建线程有哪几种??
据官方注释只有两种,打开Thread
看官方注释;如图
- 第一种:直接
new Thread
public static void main(String[] args) {
new PrimeThread().start();
}
static class PrimeThread extends Thread {
@Override
public void run() {
super.run();
}
}
- 第二种:实现
Runnable
Runnable runnable=new Runnable() {
@Override
public void run() {
}
};
new Thread(runnable).start();
说三种的让面试官好好看看注释 哈哈
2.通过Callable和FutureTask创建线程的原理??
这是网上说的第三种创建线程的方式,其实就是FutureTask
实现Runnable
接口,本质还是Runnable
,所以面试的时候完全可以说就只有两种方式创建线程。Callable
使用代码如下
Callable<Integer> callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 1;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask,"FutureTask").start();
public class FutureTask<V> implements RunnableFuture<V> {}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
通过Callable
可以拿到线程执行的返回值,如下
Integer i= futureTask.get();
3.如何保证线程依次执行??
-
Thread.join()
比如在线程1 中调用了线程2 的 join(),直到线程1执行完毕后,才会继续执行线程2;代码演示如图 -
使用CountDownLatch
- 使用阻塞队列(BlockingQueue)
LinkedBlockingQueue中维持两把锁,一把锁用于入队,一把锁用于出队,这也就意味着,同一时刻,只能有一个线程执行入队,其余执行入队的线程将会被阻塞;同时,可以有另一个线程执行出队,其余执行出队的线程将会被阻塞。
4.线程池有使用过吗,请简述下他的原理??
首先来看看线程池的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
- corePoolSize:核心线程池的数量,同时能执行的线程数量
- maximumPoolSize:最大线程数,允许的最大数量
- keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出。
- unit:keepAliveTime时间的单位,例如
1*TimeUnit.HOURS
- workQueue:阻塞队列,提交的线程会被塞到队列里
- threadFactory:线程共厂,用来创建线程用的,有提供默认的共厂,例如
Executors.defaultThreadFactory()
- handler:拒绝策略,当加入的线程超过的最大线程数时执行的一系列拒绝策略。
线程池流程用张图概括如下
源码如下,注释很详细
public void execute(Runnable command) {
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
//首先判断当前线程数是否小于核心线程数?如果小于,则直接调用`addWorker`方法
if (addWorker(command, true))
return;
c = ctl.get();
}
//workQueue.offer 向队列插入线程,返回boolean类型代表是否插入成功
if (isRunning(c) && workQueue.offer(command)) {
// 如果入队成功,我们仍然需要仔细检查是否应该添加一个线程
//因为可能存在之前的线程死掉了,或者线程关闭了因此,
// 我们重新检查状态,并在必要时回滚排队(如果已停止),或者在没有线程的情况下启动新线程。
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//往队列里插入失败,则再去尝试创建线程执行任务
else if (!addWorker(command, false)) {
//如果尝试创建线程执行任务失败则执行拒绝策略
reject(command);
}
}
5.拒绝策略默认有哪几种??
- AbortPolicy:超出最大线程数就丢弃任务并抛出RejectedExecutionException异常,默认的处理方式。
- CallerRunsPolicy:用调用者所在线程来执行当前任务
- DiscardOldestPolicy:丢弃队列最前面的任务,并执行当前线程
- DiscardPolicy:直接丢弃加入来的任务,但是不抛出异常。
6.使用线程池有什么好处??
- 可以有效防止重复创建&销毁线程带来的性能问题。
- 可以统一管理线程
- 提高响应速度,不需要等待创建线程
7.线程池数量如何设置??
- IO密集型:CPU核心线程数*2
- CPU密集型:CPU核心线程数+1
获取CPU核心线程数的方法Runtime.getRuntime().availableProcessors()
,这里指的是最大线程数
8.shutdown与shutdownNow的区别???
- shutdown将状态至为SHUTDOWN;shutdownNow至为STOP
- shutdown中断空闲的线程,正在执行的线程执行完,;shutdownNow中断所有线程
- shutdown没有返回结果;shutdownNow返回没执行的任务列表
9.interrupt() interrupted() isInterrupted()
三者的区别???`
- interrupt:中断标志位,并不能真正的停止线程,需线程自行决定是否中断
- interrupted:interrupted判断标志位是否中断,但是会讲标志位置为false
- isInterrupted:判断标志位是否中断,如果调用了interrupt(),则isInterrupted返回true
10.如何安全的关闭一个线程???
接代码演示,
之前中断线程都是自己定义一个布尔变量 放到while(boolean)
里,这种做法是不推荐的。原因是线程阻塞或者挂起的时候是不会判断自己定义的布尔变量的值
11.Thread的start()方法执行两次会发生什么???
抛出IllegalThreadStateException
异常
public synchronized void start() {
if (started)
throw new IllegalThreadStateException();
}
12.yield方法了解吗???
- 让出CPU的执行权,重新分配执行权,让出的线程还是有机会拿到CPU的执行权。
- 只是让出执行权,不会释放锁
12.sleep wait yield对锁的影响???
sleep和yield不会释放锁,wait会释放锁,被唤醒时会重新拿到锁。
13.synchorized 关键字锁普通方法和静态方法的区别?什么是可见性??
- 普通方法(也叫对象锁):锁的是当前这个类的实例
- 静态方法(也叫类锁):锁的是当前类的class对象,一个JVM虚拟机里只有一个class对象
- 什么是可见性:通俗来说可见性就是不同线程之间访问同一个变量,一个改了值另外一个线程是不知道的,最简单的操作就是给变量加上volatile关键字
14.sleep是可以被中断的吗??
可以,如果线程在sleep期被中断,则会抛出一个异常,可以查看问题10
15.ThreadLocal是什么??
ThreadLocal是Java里一种特殊的变量。ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
在内部实现上,每个线程内部都有一个ThreadLocalMap,用来保存每个线程所拥有的变量副本。
16.Executors ExecutorService的 Executor区别,为什么不推荐使用Executors??
Executors
像是一个工具类,也可以理解为工厂吧,里面大部分方法都是static
的,return对象是Executor
的实现类Executor
也是接口,内部只有一个方法就是执行Runnable;void execute(Runnable command);
ExecutorService
是个接口实现Executor
,提供了其他方法,例如shutdown() shutdownNow()
真正的线程池是实现ExecutorService
的类 如图所示
为什么不推荐使用Executors
?
可以理解为:是不推荐使用Executors
默认的方法,比如
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这里的maximumPoolSize
传的无限大,这样肯定是不行的,我们应该按照自己的需求和业务来自定义我们的线程池,可以参考问题4来自定义参数,初始化我们的线程池
17.使用Okhttp的时候为什么不用自带的线程池(我简历写了okhttp的封装)??
可以说,默认的线程池不满足我们的需求,比如我们需求一次最多请求10个,最多同时支持3个,对应corePoolSize==3
maximumPoolSize==10
,我们看看OkHttp
默认的线程池是怎么样的呢?
18.双重校验锁单例模式为什么要判断两次空??
加入现在有个两个线程同时走到第一个锁那,此时单利对象为空,都进到了if语句里,此时CPU时间片轮转,第一个线程处于等待状态,第二个还在正常执行;过一会第一个线程拿到执行权,继续执行的时候如果不再次判断一次是否为空,那么就会创建两个对象,失去了单例的意义;
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance(){
if(singleton==null){
//假设两个线程同时执行到这,CPU调度,第一个线程让出执行权
synchronized (Singleton.class){
if(singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
}