收集常见的问题, 以后或许用得到
线程池的原理, 为什么要创建线程池? 创建线程池的方式?
答: 当向线程池提交一个任务的时候.
先看线程池中的核心线程是否有空闲的, 如果有创建一个工作线程来执行任务. 如果核心线程都在工作, 那么进入下一步
判断任务队列是否满了, 如果任务队列未满, 则把任务存储到任务队列, 执行下一步. 如果满了, 执行拒绝策略.
添加到任务队列之后, 再判断核心线程是否有空闲的, 如果没有空闲的, 那么尝试创建新的非核心线程执行任务.
在实际的生产环境中, 线程的数量必须得到控制, 盲目的大量创建线程对系统性能是有伤害的, 合理使用线程好处:
减少在创建和销毁现场上所消耗的时间和系统资源
提高响应速度, 无需创建可以直接运行
提高线程的可管理性. 使用线程池可以进行统一分配, 调优和监控, 但是要做到合理利用线程池, 必须对其原理了如指掌.
创建线程池的方式:
Executors 框架, 有可能导致 OOM 异常
手动创建线程池, 我们更明白线程池的参数, 方便调优
参考: 多线程之线程池 (六)
线程的生命周期, 什么时候会出现僵死进程?
线程是轻量级的进程, 进程可以说是线程的容器.
image
参考: 多线程之并发基础 - 线程状态与操作 (三)
说说线程安全问题, 什么是实现线程安全, 如何实现线程安全?
并发编程中最常出现的情形就是多个线程共享一个资源, 这些共享的资源很可能导致错误或者数据不一致的情形, 需要想办法来解决这种问题. 线程安全是多线程领域的问题, 线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题.
线程安全实现方式:
互斥同步, 加锁, 悲观方案, 保证共享数据同一时刻只有一个线程访问., 互斥是因, 同步是果.
非阻塞同步, CAS, 乐观方案, 先进行操作, 如果没有其他线程也进行操作, 那么就操作成功了, 如果有其它线程也在操作共享数据, 那么再重试.
无同步方案, 一般为纯代码, 有一些特性, 如不依赖堆上的公用系统资源
参考: Java 高效并发 (九)
synchronized 即可修饰非静态方式, 也可修饰静态方法, 还可修饰代码块, 有何区别?
答: synchronized 修饰非静态同步方法时, 锁住的是当前实例; synchronized 修饰静态同步方法时, 锁住的是该类的 Class 对象; synchronized 修饰静态代码块时, 锁住的是 synchronized 关键字后面括号内的对象.
创建线程池有哪几个核心参数? 如何合理配置线程池的大小?
corePoolSize: 线程池的基本大小. 当提交一个任务的时候, 线程池就会创建一个新的线程执行任务, 即使核心线程池中有空闲线程, 也会新建, 直到线程池中的数量等于 corePoolSize 就不再创建. 如果调用了线程池的 prestartAllCoreThreads() 方法, 线程池会提前创建并启动所有的线程.
maximumPoolSize: 线程池允许创建的最大线程数. 当使用无界队列的时候, 这个参数就没什么效果了.
keepAliveTime: 线程池的工作线程空闲以后, 保持存活的时间, 如果任务多, 并且任务执行时间段, 可以调大时间, 提高线程的利用率.
unit 保活时间的单位
workQueue: 任务队列, 用于保持或等待执行的任务阻塞队列. 有如下队列可供选择:
ArrayBlockingQueue: 基于数组结构的有界队列, 此队列按 FIFO 原则对元素进行排序
LinkedBlockingQueue: 基于链表的阻塞队列, FIFO 原则, 吞吐量通常高于 ArrayBlockingQueue.
SynchronousQueue: 不存储元素的阻塞队列. 每个插入必须要等到另一个线程调用移除操作.
PriorityBlockingQueue: 具有优先级的无阻塞队列
threadFactory: 用于设置创建线程的工厂.
handler: 拒绝策略, 当队列线程池都满了, 必须采用一种策略来处理还要提交的任务. 在实际应用中, 我们可以将信息记录到日志, 来分析系统的负载和任务丢失情况 JDK 中提供了 4 中策略:
AbortPolicy: 直接抛出异常
CallerRunsPolicy: 只用调用者所在的线程来运行任务
DiscardOldestPolicy: 丢弃队列中最老的一个人任务, 并执行当前任务.
DiscardPolicy: 直接丢弃新进来的任务
线程池中线程的数量过大和过小都无法使系统的性能发挥到最优, 确定线程池的大小可以考虑下面的角度:
任务性质: CPU 密集, IO 密集, 和混合密集
任务执行时间: 长, 中, 低
任务优先级: 高, 中, 低
任务的依赖性: 是否依赖其它资源, 如数据库连接
建议使用有界队列, 防止撑爆内存
参考: 多线程之线程池 (六)
volatile,ThreadLocal 的使用场景和原理?
JMM 中主要是围绕并发过程中如何处理原子性, 可见性和有序性三个特性来建立的. 最终可以保证线程安全性.
一个变量定义为 volatile 之后, 它将具有两种特性:
保证次变了对所有线程的可见性, 一条线程修改了这个值, 新值对其它线程是可以立即得知的.
禁止指令重排优化.
Synchronized 保证了原子性, 可见性与有序性, 它的工作时对同步的代码块加锁, 使得每次只有一个线程进入代码块, 从而保证线程安全. synchronized 反应到字节码层面就是 monitorenter 与 monitorexit.
Volatile 适合做什么?
适合做标量, 当一个线程对某个变量进行读写操作, 而其它线程仅仅进行读操作的时候, 是可以保证 volatile 的正确性的. 如下:
- volatile bool stopped;
- public void stop(){
- stopped = true
- }
- while(!stoppped){
- // 执行操作
- }
参考: 多线程之 volatile 与 synchronized(二)
ThreadLocal 什么时候会出现 OOM 的情况? 为什么?
答: 当一个线程调用 ThreadLocal 的 set 方法设置变量时候, 当前线程的 ThreadLocalMap 里面就会存放一个记录, 这个记录的 key 为 ThreadLocal 的引用, value 则为设置的值. 如果当前线程一直存在而没有调用 ThreadLocal 的 remove 方法, 并且这时候其它地方还是有对 ThreadLocal 的引用, 则当前线程的 ThreadLocalMap 变量里面会存在 ThreadLocal 变量的引用和 value 对象的引用是不会被释放的, 这就会造成内存泄露的.
参考: 使用 ThreadLocal 不当可能会导致内存泄露
借 ThreadLocal 出现 OOM 内存溢出问题再谈弱引用 WeakReference
最后
持续更新, 多线程相关问题
题目参考
Java 程序员从阿里拿到 offer 回来, 这些面试题你会吗?
来源: http://www.jianshu.com/p/51ec910db964