如何理解线程池中的参数设计
- 你的线程池的参数怎么配置?线程数量设置多少合理?
- 如何确定一个线程池中的人物已经完成了
- 为什么不建议使用java自带的Executors创建线程池
- 线程池里面的阻塞队列设置多少合理?
考察:了解你对技术的掌握程度,|对于技术的理解、场景问题
线程池的参数有哪些
- 核心线程数
常驻在线程池中的工作线程数量 - 最大线程数
表示线程池中最大能容纳的线程数量(扩容) - 阻塞队列
当核心线程跑满的时候,存储任务的容器 - 等待时间
- 等待时间单位
- 拒接策略
超过线程池能够处理的容量的时候的保护机制 - 线程工厂
线程池的设计
池化技术->实现了对线程的复用(一个技术的产生背景)
- 线程数量不可控
- 线程的频繁创建和销毁带来的开销
ThreadPoolExector(Java实现)
通过生产者-消费者模型来解决线程服用问题(技术方案)
可以把基于阻塞队列的生产者消费者模型放大一下,就是分布式消息队列。
public class ThreadPoolDemo {
static Queue<Runnable> tasks = new LinkedList<>();
static class WorkThread implements Runnable {
@Override
public void run() {
while (true) {
Runnable task = tasks.poll();
if (task != null) {
System.out.println("工作线程开始执行:" + Thread.currentThread().getName());
task.run();
}else {
System.out.println("当前没有任务执行:" + Thread.currentThread().getName());
synchronized (WorkThread.class){
try {
WorkThread.class.wait();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) {
WorkThread workThread = new WorkThread();
new Thread(workThread).start();
Scanner scanner = new Scanner(System.in);
while (true){
String s = scanner.nextLine();
tasks.add(()->{
System.out.println(Thread.currentThread().getName()+"数据定时同步的任务,开始执行" + s);
}) ;
synchronized (WorkThread.class){
WorkThread.class.notify();
}
}
}
}
线程池的价值是什么?
架构思维
java开发,就真的只要会CRUD
职业发展-》架构,技术经理
- 生产者消费模型(支付,第三方支付,异步发送到第三方支付)
- 扩容和缩容的思想,工作线程的创建和销毁
- 阻塞队列
- 保护策略(拒绝策略),考虑系统的稳定性
线程池的执行
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
- 如果正在运行的线程少于 corePoolSize,请尝试使用给定命令启动一个新线程作为其第一个任务。对 addWorker 的调用以原子方式检查 runState 和workerCount,从而通过返回 false 来防止在不应该添加线程时添加线程的错误警报。
- 2. 如果任务可以成功排队,那么我们仍然需要仔细检查是否应该添加线程(因为自上次检查以来现有线程已死亡),或者自进入此方法以来池已关闭。因此,我们重新检查状态,并在必要时回滚排队(如果停止),或者启动一个新线程(如果没有)。 3. 如果我们无法将任务排队,那么我们尝试添加一个新线程。如果失败,我们就知道我们已关闭或饱和,因此拒绝该任务。
int c = ctl.get();
//1,先判断运行的线程是否少于核心线程,是的话addWork方法会启动一个新线程
//addWorker会自动检查runState和 workerCount,这样可以防止误报警
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2,如果任务可以成功排队,我们检查是否添加线程,
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);
}
//3,如果我们无法将任务排队,那么尝试添加一个新的线程,如果已关闭或饱和,拒绝该任务。
else if (!addWorker(command, false))
reject(command);
}
线程池的拒绝策略
线程池的拒绝策略是在任务提交到线程池时,当线程池无法接受新的任务时,如何处理这些被拒绝的任务。下面列举了一些常见的线程池拒绝策略:
-
AbortPolicy(默认策略):当线程池无法处理新的任务时,会抛出RejectedExecutionException异常。
-
CallerRunsPolicy:当线程池无法处理新的任务时,会将任务退回给调用线程来执行,也就是提交任务的线程自己执行任务。
-
DiscardPolicy:当线程池无法处理新的任务时,会默默地丢弃被拒绝的任务,不会抛出任何异常。
-
DiscardOldestPolicy:当线程池无法处理新的任务时,会先丢弃执行队列中最早的任务,然后尝试重新提交被拒绝的任务。
我们看看源码
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
//由调用线程执行
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
/**
* 抛出异常
*/
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
/**
* 静默的丢弃被拒绝的任务
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
/**
* 被拒绝任务的处理程序,该处理程序将丢弃最早的未处理任务
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
阻塞操作:当队列为空时,从队列中获取元素的操作将被阻塞,直到队列中有可用元素为止。同样,当队列已满时,向队列中添加元素的操作也将被阻塞,直到队列有空闲位置为止。这种阻塞的特性使得线程能够方便地进行同步等待,而不需要忙等或轮询。
Synchronized的锁升级
无锁-》偏向锁-〉轻量级锁-》重量级锁。
- 什么是偏向锁,什么是轻量级锁,什么是重量级锁。
- 为什么要设计锁升级?
- Synchronized是提供了锁的公平性吗?
- Synchronized锁标记怎么存储的?
- 重量级锁为什么称为重量级锁?
java5之前,是没有锁升级这个概念的
无锁-》重量级锁
加锁会带来性能开销:
- 内核指令的调用,涉及到上下文切换
- 线程阻塞唤醒,涉及到上下文切换‘
消耗cpu资源,影响程序的执行性能!
加锁的方式从并行变成了串行。
两个层面的优化
使用层面的优化
控制加锁的位置,也就是锁的范围。
JVM层面的优化
1,编译器的优化,深度编译(锁的膨胀和锁的消除)
2,锁的升级
思考:能不能在让线程阻塞之前,就竞争到锁呢?
轻量级锁(自旋锁)
自旋竞争锁,通过循环尝试获取锁来竞争到锁资源。平衡循环次数
前提是:通过自旋尝试获得锁的代价,要比线程进入到阻塞代价更低
价值
提炼出有价值的架构思维
如何平衡好性能和安全性之间的关系
库存,防止超卖和少卖
ConcurrenthashMap 1.7的版本锁的是Segment,1.8版本锁的是Node节点
Mysql,表锁,行锁,间隙锁,临键锁,MVCC乐观锁