-
什么是线程 什么是进程
答:进程 相当于一个加载到内存当中静态资源文件, 线程 是进程中的某个实体 是cup 调度和分派的基本单位
并发属于逻辑性定性,并行属于物理级别 -
线程的实现方式 ()
Runable 接口 Thread 类 ,callable 接口
callable&& Future 能有返回值是因为 Futuretask 实现 RunnableFuture 接口 会去执行 run 方法
用 volatile 修饰的 state 这个状态值 去判断执行操作(4中流程 看源码 状态执行流程) ,最终去执行call 方法 通过set ()方法去执行
set() 去设置结果值 (用cas 比较自旋的方式去修改状态值 通知其他线程不用去获取值去塞了) 然后通过get 获取
get 方法中也是去跟据线程状态去阻塞等待 ,利用cas去判断状态值 最总等于 NORMAL才返回
四种流程
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
- 线程的生命周期和状态
4. 线程池
线程池分为2种 普通线程池 (ThreadPoolExecutor)和定时线程池 (ScheduledThreadPoolExecutor 他还继承了前者)
面试的时候会问到 四种
一、四种线程池
Java通过Executors提供四种线程池,分别为
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 LinkedBlockingQueue
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。LinkedBlockingQueue
newScheduledThreadPool 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。 DelayQueue
newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 SynchronousQueue
二、核心类
四种线程池本质都是创建ThreadPoolExecutor类,ThreadPoolExecutor构造参数如下
int corePoolSize, 核心线程大小
int maximumPoolSize,最大线程大小
long keepAliveTime, 超过corePoolSize的线程多久不活动被销毁时间
TimeUnit unit,时间单位
BlockingQueue workQueue 任务队列
ThreadFactory threadFactory 线程池工厂
RejectedExecutionHandler handler 拒绝策略
三、阻塞队列
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列 (数组+锁)
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列 (链表+锁)(常用)
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列 (堆+锁)
DelayQueue: 一个使用优先级队列实现的无界阻塞队列 ( Priorityqueue+锁)
Priorityqueue:heap=完全二叉树+特性(小根堆:根比儿子小,大根堆:根比儿子大)
SynchronousQueue: 一个不存储元素的阻塞队列(常用)
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列
六 拒绝策略 默认使用第一种
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务 ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
七线程池的体系图谱
八.线程池分解
** 1.ThreadPoolExecutor (普通)线程池两种执行方法 两者底层都是掉了同一个方法 execute()**
excuete()
submit() 有返回值 多了实现RunnableFuture 接口
submit 自身传入一个是 futuretask, excuete() 就是普通的一个task
源码分析
4个步骤进行:
- (1).如果少于corePoolSize线程运行,
- (2).如果大于corePoolSize线程运行,加入队列当中
- (3).前两个都不满足 就添加一个新线程,
- (4)如果3加入不了 addwork 工作方法里就执行handler拒绝策略,
execute(){
if (workerCountOf© < corePoolSize) { //1
if (addWorker(command, true);
}
if (isRunning© && workQueue.offer(command)) { //2
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0) //3
addWorker(null, false);
}
else if (!addWorker(command, false)) //4
reject(command);
}
addWorker(){
//自旋
1.根据状态 只在必要时检查队列是否为空
2. 自旋检查核心数量 检验配置参数
3. Worker w = new Worker(firstTask); 加入线程 执行run 方法
4. workers.add(w) 根据状态统计线程数量 (workers 非线程安全的 所以代码中用到了 ReentrantLock 锁)
5. workerAdded =true 如果可以加入work
6. t.start(); 那就会调用 这个 t 是线程本身 执行线程
}
Worker extends AbstractQueuedSynchronizer implements Runnable // 他实现Runable 接口 必定会执行 run方法
run(){
runWorker(){
(task != null || (task = getTask()) != null) // 会去调用
1. getTask(){ //去队列里面取值 在四个步骤第二个的时候 塞值workQueue.offer(command)
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
}
2. 根据状态会去校验是否需要关闭
3.task.run //这里就是我们自己重写的run 方法
}
4.最终还会调用一个processWorkerExit (){
(1)判断时候需要一处当前线程 还是要继续 addWorke();
}
}
流程图
2. 定时线程池 ScheduledThreadPoolExecutor
schedule() //定时任务
scheduleAtFixedRate //固定速率连续执行 :必须等上次任务执行完才会执行下个 (源码中 传入的时间是正数数)
scheduleWithFixedDelay// 非固定速率连续执行:延时时间内任务没执行完,任务会启动多个并行执行 (源码中 传入的时间是负数)
ScheduledThreadPoolExecutor.DelayedWorkQueu//延迟队列
schedule(){
RunnableScheduledFuture<?> t = decorateTask()//返回值
delayedExecute(t){
if (isShutdown())
reject(task); //线程池关闭的话就拒绝
else
super.getQueue().add(task); //往延迟队列去塞任务 (会造成阻塞 因为往队列塞值会上锁)
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else「
ensurePrestart();{ //判断核心线程池大小
addWorker(null, true);
}
}
}
}
ScheduledThreadPoolExecutor .public void run() {
boolean periodic = isPeriodic(); //判断是否周期
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime(); //下次运行时间
reExecutePeriodic(outerTask);
}
}
重点
super.getQueue().add(task) //任务如何往延迟队列塞值{
java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue#add{
public boolean offer(Runnable x) { //涉及到二叉树排序算法
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = size;
if (i >= queue.length) //判断是否扩容
grow();
size = i + 1;
if (i == 0) { //如果第一个 把task放进去 排序
queue[0] = e;
setIndex(e, 0);
} else {
siftUp(i, e);
}
if (queue[0] == e) {
leader = null;
available.signal(); //如果是第一个就唤醒 使用条件锁 condition
}
} finally {
lock.unlock();
}
return true;
}
}
}
九.单列模式
饿汉模式 :线程安全
懒汉模式:非线程安全 但是如果在静态方法上面加锁(方法级别的锁) 是线程安全的 (同步锁 ,效率低)
如果在码块中加锁也是非线程安全(涉及到重排序的问题) 所有衍生了双重检查方式来解决重排问题
内部类的方式
枚举的方式
细节
1.jsp 查看java 进程 , jstack *** 查看线程状态可以排查线程死锁
2.join 保证线程的顺序 靠着native修饰的本地方法wait 方法 ,让主线去等待
3. 可以设置线程池监控