面试题:
- 什么是进程和线程
- 进程是计算机获取资源的最小单位
- 线程是计算机调度、运行的最小单位
- 实现线程的三种方式
- 继承Thread类
- 实现runnable接口
- 覆写run()方法
- 实现类的实例交给thread类封装,调用有参构造生成线程
- 适合无返回值
- 实现callalbe接口
- 覆写cal()方法
- 实现类的实例交给futuretask类封装,再交给thread类封装,调用有参构造生成线程
- 适合有返回值
- 线程池
- 继承Thread类和实现runnable接口的区别?
- 相同点:
- thread类实现了后者
- 都重写了run方法
- 不同点
- 实现runnable接口不具有单继承的局限性,更加健壮
- 实现runnable接口可以更好地实现对资源的共享
- 相同点:
- 线程的常用方法
- yeild方法:使当前线程从执行状态变为就绪状态——放弃锁资源
- sleep方法:使当前执行线程休眠,休眠结束可以放回执行状态——不放弃锁资源
- join方法:通常用于在main()主线程内,等待其它线程完成再结束main()主线程,不会放弃锁资源,线程会等
- deamon方法:为程序提供服务的线程,在非后台线程全部结束后,杀死所有的后台线程(PS:main() 属于非后台线程|使用setDaemon() 方法将一个线程设置为后台线程。)
- 线程状态:
- 新建状态:刚new 未调用start()方法
- 就绪状态:调用start()方法 处于就绪队列,等待jvm线程调用器调用
- 运行状态:当就绪状态的线程获取到资源,执行run()方法,处于运行状态。可以因为具体情况不同变为阻塞状态、就绪状态、死亡状态
- 阻塞状态:
- 等待阻塞:收到wait通信(用notify|notifyAll唤醒)
- 同步阻塞:请求的资源正被其他线程访问,获取锁资源失败
- 其他阻塞:通过调用线程的sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
- 死亡状态:完成任务或者其他终止条件发生
- 线程的通信方法
- wait-会释放锁(sleep() 和 yield() 并没有释放锁)
- notify
- notifyall
- PS:实际上,只有在同步控制方法或同步控制块里才能调用wait() 、notify() 和 notifyAll()。
- wait和sleep的区别
- Sleep是thread类中的静态方法 不会放弃锁资源 睡眠时间结束以后 直接回到运行
- Wait是object中的方法 它使线程处于等待状态放弃锁资源 需要通过notify或者notifyall进行唤醒Run方法与start方法
- synchronized锁作用
- 确保线程互斥地访问同步资源
- 保证共享变量的修改能够及时可见
- 有效地解决重排序问题
- synchronized实现原理
- 加锁:monitor enter
- 解锁:monitor exit
- 总结:synchronized基于jvm内置锁实现 依赖于操作系统的互斥锁。主要通过monitor enter和monitor exit实现。当有线程获取到资源的时候 monitor enter计数不为0 其他线程阻塞 当线程释放锁 monitor exit监视器将计数变为0 阻塞的线程此时可以竞争锁资源。Jdk1.5之后对锁进行了优化,锁有了一个升级的过程,Synchronized会从无锁升级为偏向锁,再升级为轻量级锁,最后升级为重量级锁。提升了性能。
- synchronized的优化+无锁、偏向锁、轻量级锁、重量级锁区别
- 偏向锁:单线程时无需重复上锁
- 轻量级锁:未获取锁资源的线程自旋——会产生忙等(其他线程原地自旋空耗cpu)
- 重量级锁:自旋超过最大自旋次数就挂起
- sychronized和lock锁的区别
- sychronized锁基于jvm的互斥锁实现,其锁状态无法判断,支持少量同步,可以自动释放锁
- lock是一个接口,我们常用其实现类进行加锁,可以判断锁状态,支持大量同步,需要手动在finally解锁,不然会造成死锁。
- 什么是Threadlocal?
- 称为线程本地变量,用作线程隔离,解决线程安全问题
- 通过为每个线程提供一个独立的变量副本解决并发访问的冲突问题
- 键值对形式,key为副本,value为我们存储的值。只有key和线程对应时才能取值,其他线程无法取值
- Threadlocal原理
- PS:ThreadLocalMap中用于存储数据的entry定义,使用了弱引用,可能造成内存泄漏。
- 解决方法:
- l 使用完线程共享变量后,显式调用ThreadLocalMap.remove方法清除线程共享变量;
- l ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。
- 线程池的作用
- 控制并发数量:并发过多抢占资源可能会导致阻塞
- 线程的复用:频繁得创建、销毁线程,会影响性能/效率
- 管理线程的声明周期
- 线程池的执行流程
- 总结:核心线程=> 等待队列 => 非核心线程 => 拒绝策略
- l 如果有空闲的线程直接使用,没有空闲的线程并且线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
- 线程数量达到了corePoolSize,则将任务移入队,列等待空闲线程将其取出去执行(通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源,整个getTask操作在自旋下完成),如果队列已满,新建线程(非核心线程)执行任务,空闲下来以后,非核心线程会按照时间策略进行销毁
- 队列已满,总线程数又达到了maximumPoolSize 最大线程数,就会执行任务拒绝策略。
- 线程池的七大核心参数
- 核心线程数
- 最大线程数
- 空闲时间长
- 空闲时间单位
- 工作队列
- 线程工厂(推荐 Executors.defaultThreadFactory)
- 拒绝策略
- 线程池的四种拒绝策略
- 报错并返回异常
- 报错不返回
- 抛弃旧任务
- 调用线程去执行
- 常见的四种线程池
- 可缓存 CachedThreadPool
- 没有核心线程 最大数量为Integer最大值
- 有空闲线程则复用,没有则新建
- 空闲时长为60s
- 适用负载轻、短期异步的任务
- 固定长 FixedThreadPoo
- 核心线程数==最大线程数
- 创建至最大线程数才会开始复用
- 适合长期任务
- 单个线程 SingleThreadPoo
- 仅有一个线程执行
- 任务遵循先进先出原则
- 适用于队列形式的任务
- 可调度 SingleThreadPoo
- 唯一有延迟执行和周期执行任务的线程池
- l 适用:周期性执行任务的场景(定期的同步数据)
- 自定义:略
- 可缓存 CachedThreadPool
- 线程池常用方法:
- 执行:
- excute:ThreadPoolExecutor的核心方法,提交一个任务到线程池执行
- Submit :实际调用的还是excute方法。通过future来获取任务执行结果
- 中断:
- shutdown:不会立即中断线程池,先暂停接受任务,等任务缓存队列的任务执行完后进行终止
- shutdownnow:立即进行终止,会尝试打断正在执行的任务,清空任务队列,返回未执行的任务
- isTerminated:调用shutdown方法后我们可以在一个死循环里面用isTerminated方法判断是否线程池中的所有线程已经执行完毕,如果子线程都结束了,我们就可以做关闭流等后续操作了。
- 执行: