如何理解线程池中的参数设计

如何理解线程池中的参数设计

  • 你的线程池的参数怎么配置?线程数量设置多少合理?
  • 如何确定一个线程池中的人物已经完成了
  • 为什么不建议使用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乐观锁

你可能感兴趣的:(java,并发编程)