1.如何等到所有子线程执行完毕再往下执行主线程业务?(如何判断所有子线程执行完毕主线程才继续执行?)

1)利用CountDownLatch的计数器实现(JUC包)
创建并执行线程,在子线程run方法中,调用countDown()方法进行计数-1
创建线程完毕后,在主线程方法调用await()方法,该方法会等待所有子线程完毕计数器为0,主线程才会唤醒往下执行.
2)利用ExecutorService的shutdown + awaitTermination方法,可以等到所有子线程执行完毕才执行主线程

2.Runnable和Callable的区别?

相同点:
1) 都是接口
2) 都可以用来编写多线程代码
3) 两者都可以调用Thead.start()来启动线程
不同点:
1) 最大区别,Runnable没有返回值,而实现Callable接口的任务线程能返回执行结果
2) Callable接口实现类中的run方法允许异常向上抛,可以在内部处理,try catch,但是Runnable方法的异常必须在内部处理,不能抛出.
3) Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执行,Callable只能通过线程池来执行.

3.synchronized和volatile的区别?

1)volatile只能作变量修饰符,而synchronized则可以修饰代码快或方法
2)volatile不需要加锁,比synchronized更轻量级,不会阻塞线程;而synchronized加锁,会导致线程阻塞(互斥性)
3)synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性
4)synchronized会执行编译优化(对字节码重新排序),编译优化有可能导致运行过程中异常.而volatile禁止字节码重新排序,不会引发编译优化导致的异常.
原子星:一个操作不能被打断,要么全部执行完毕,要么不执行
可见性:一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量这种修改(变化)

4.synchronized和Lock的区别?

1)语法不同:
synchronized是Java的关键字或修饰符,在jvm层面,修饰方法或代码块
Lock不是修饰符,是一个接口(加锁的工具类,该类提供很多方法加锁,释放锁)
2)释放锁不同:
synchronized已获取锁的线程执行完同步代码,会释放锁,且线程执行发生异常,jvm会自动让线程释放锁(不管成功还是失败,都会自动释放锁)
Lock必须手动在finally中释放锁(必须手动释放锁)
3)死锁情况不同:
synchronized在发生异常实惠自动释放占有的锁,因此不会出现死锁
Lock发生异常时,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生
4)锁判断不同
synchronized无法判断当前线程的上锁状态
Lock可以判断当前线程的上锁状态 tryLock unLock isLock
5)锁类型不同
synchronized是可重入 不可判断 非公平
Lock是可重入 可判断 可公平
公平性(线程等待时间越长,有限获取锁)

5.什么是死锁?怎么防止死锁?

死锁是指多个线程因竞争共享资源而造成的一种互相等待的僵局
死锁的四个不要条件:
1) 互斥:一次只有一个线程可以使用一个资源,其他线程不能访问已分配给其他线程的资源
2) 占有且等待:当一个线程在等待分配得到其他资源时,其继续占有已分配得到二资源
3) 非抢占:不能强行抢占线程中占有的资源
4) 循环等待:存在一个封闭的线程链,使得每个资源至少占有此链中下一个线程所需要的一个资源.
如何预防死锁?
1) 加锁顺序(线程按顺序加锁)
2) 加锁时限(线程请求锁上期限,超时就放弃,同时地方自己占有的锁)死锁检测,redis作为锁+过期时间(50s) Reddison

6.JDK的四种线程池创建线程的方式?线程池的执行原理?(重点)

ExecutorService接口,JDK线程池接口
1) 固定线程数的线程池(newFixedThreadPool)
ExecutorService es = Executor.newFixedThreadPool(5);
这种线程池里面的线程被设计成存放固定数量的线程,具体线程数可以考虑CPU核数*N(N一般会2),这种线程池适合用在稳定且固定的并发场景.
2) 缓存的线程池(newCachedThreadPool)
ExecutorService es = Executor.newCachedThreadPool();
这是一个无限扩大的线程池.
适合处理执行时间比较小的任务
线程空闲时间超过60s就会被杀死,所以长时间处于空闲的状态的时候,这种鞋线程池几乎不占用资源
使用该线程池时,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题
3) 单个线程的线程池(newSingThreadExecutor)
ExecutorService es = Executor.newSingleThreadExecutor();
该线程池可以保证所有任务按照指定顺序执行,所以这个比较适合哪些需要按序执行任务的场景
4) 固定个数的线程池(newScheduleThreadPool)(延迟执行线程)
Executors.newScheduleThreadPool(5);
和固定线程是的线程池(newFixedThreadPool)类似.
但是它可以执行延迟任务,也可以执行带有返回值的任务.

7.线程池的核心参数有哪些?(7个)

corePoolSize:核心线程数(默认1).allowCoreThreadTimeOut=false为默认值
如果设置allowCoreThreadTimeout=false后,当前线程数大于corePoolSize,如果线程空闲等待时间超过keepAliveTime,则该线程会被回收
如果设置allowCoreThreadTimeout=true后,当前线程数小于corePoolSize时,线程池的线程空闲等待时间超过keepAliveTime,也会被回收
maximumPoolSize:最大线程数(默认:Integer.MAX_VALUE)
keepAliveTime:空闲线程存活时间(默认值60s)
unit:时间单位(秒)
workQueue:工作队列大小(默认值:Integer.MAX_VALUE)
threadFactory:线程工厂
handler:拒绝策略(默认AbortPolicy策略)
JDK默认的拒绝策略有四种:
\1. AbortPolicy:丢弃任务并抛出RejectedException异常(默认值)
\2. DiscardPolicy:丢弃任务,但不抛异常.可能导致无法发现系统的异常状态
\3. DiscardOldestPolicy:丢弃对接最前面的任务,然后重新提交被拒绝的任务
\4. CallerRunsPolicy:由调用线程处理该任务

8.线程池的任务执行过程怎么样?

当前线程数是否大于corePoolSize
小于:
直接创建线程
>=corePoolSize,再判断workQueue是否已经满了
未满:
创建临时线程存入工作队列尾部
满了:
判断是否大于maximumPoolSize
小于
直接创建线程
大于或等于
执行拒绝策略