小米面试:如何实现优先级线程池?

简介: 我们知道,线程池中的所有线程都是由统一的线程工厂来创建的,当我们指定线程工厂时,线程池中的所有线程会使用我们指定的线程工厂来创建线程;但如果没有指定线程工厂,则会使用默认的线程工厂 DefaultThreadFactory 来创建线程,核心源码如下:```javaDefaultThreadFactory() { @SuppressWarnings("removal") SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() :

我们知道,线程池中的所有线程都是由统一的线程工厂来创建的,当我们指定线程工厂时,线程池中的所有线程会使用我们指定的线程工厂来创建线程;但如果没有指定线程工厂,则会使用默认的线程工厂 DefaultThreadFactory 来创建线程,核心源码如下:

DefaultThreadFactory() {
   
   
    @SuppressWarnings("removal")
    SecurityManager s = System.getSecurityManager();
    group = (s != null) ? s.getThreadGroup() :
                          Thread.currentThread().getThreadGroup();
    namePrefix = "pool-" +
                  poolNumber.getAndIncrement() +
                 "-thread-";
}

那么问题来了,面试官问的是“如何实现优先级线程池?”,为什么我们一上来先讲了线程工厂呢?

这是因为,当我们讲到线程池优先级的时候,我们首先会想到线程的优先级,所以按照惯性思考,当面试官问到如何使用实现优先级线程池时,我们首先会考虑是不是在创建线程池的时候,可以通过某种方法来创建不同的线程优先级,从而实现优先级线程池?这就是开头我们一上来就讲线程工厂的原因。

那在线程工厂中如何设置线程的优先级呢?

它的设置也比较简单,如下代码所示:

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomThreadPoolExecutorDemo {
   
   
    public static void main(String[] args) {
   
   
        // 自定义线程工厂
        ThreadFactory threadFactory = new CustomThreadFactory();
        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0, 
                                                             TimeUnit.MILLISECONDS, 
                                                             new LinkedBlockingQueue<>(), 
                                                             threadFactory);
        // 提交任务
        executor.execute(() -> System.out.println("Task 1"));
        executor.execute(() -> System.out.println("Task 2"));
        // 关闭线程池
        executor.shutdown();
    }

    static class CustomThreadFactory implements ThreadFactory {
   
   
        @Override
        public Thread newThread(Runnable r) {
   
   
            Thread thread = new Thread(r);
            // 设置线程优先级为最低优先级
            thread.setPriority(Thread.MIN_PRIORITY); 
            return thread;
        }
    }
}

但是这种方式也有问题,那就是线程工厂是统一的,所以即使能在线程工厂中设置线程的优先级,那么也是将整个线程池中的所有线程都设置成统一的优先级了,而不能解决咱们本文提出的问题的,那如何才能实现优先级线程池呢?

1.优先级线程池实现思路

转念一想,既然不能在线程优先级上下功夫,但我们是否可以在线程池的任务队列上动点心思呢?

此时我们想到,可以使用 PriorityBlockingQueue 优先级队列来对任务进行排序啊(PriorityBlockingQueue 天生支持按照优先级自动排序任务的),这样不就能保证优先级高的任务会被线程池优先获取并执行了嘛

所以,有时候一条路走不通的时候,我们可以尝试换一个思路再试试。

2.优先级队列使用

我们先来测试一下 PriorityBlockingQueue 的使用,以尝试其可行性,示例代码如下:

import java.util.concurrent.PriorityBlockingQueue;

public class PriorityBlockingQueueExample {
   
   
    public static void main(String[] args) {
   
   
        PriorityBlockingQueue<Task> priorityQueue = new PriorityBlockingQueue<>();

        // 添加任务到优先级队列
        priorityQueue.add(new Task("Task 1", 1));
        priorityQueue.add(new Task("Task 4", 4));
        priorityQueue.add(new Task("Task 3", 3));
        priorityQueue.add(new Task("Task 2", 2));

        // 从优先级队列中取出任务并执行
        while (!priorityQueue.isEmpty()) {
   
   
            Task task = priorityQueue.poll();
            if (task != null) {
   
   
                task.execute();
            }
        }
    }

    static class Task implements Comparable<Task> {
   
   
        private String name;
        private int priority;

        public Task(String name, int priority) {
   
   
            this.name = name;
            this.priority = priority;
        }

        public void execute() {
   
   
            System.out.println("Executing task: " + name);
        }

        @Override
        public int compareTo(Task o) {
   
   
            return Integer.compare(this.priority, o.priority);
        }
    }
}

以上程序的执行结果如下:
image.png
从上述结果和代码可以看出,我们添加任务的顺序是:1、4、3、2,但最终会按照优先级排队执行的顺序是:1、2、3、4,执行结果符合我们的预期,优先级高的任务先被执行了(数字越小,优先级越高)。

3.优先级线程池

因此,我们实现的优先级线程池的最终代码如下:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class PriorityThreadPool {
   
   
    public static void main(String[] args) {
   
   
        BlockingQueue<Runnable> queue = new PriorityBlockingQueue<>(1000);

        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1,
                0, TimeUnit.SECONDS, queue
        );

        for (int i = 0; i < 100; i++) {
   
   
            int finalI = i;
            executor.execute(new PriorityTask(i, () -> {
   
   
                try {
   
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
   
                    throw new RuntimeException(e);
                }
                System.out.println("优先级:" + finalI);
            }));
        }
    }

    static class PriorityTask implements Runnable, Comparable<PriorityTask> {
   
   
        private final int priority;
        private final Runnable task;

        public PriorityTask(int priority, Runnable task) {
   
   
            this.priority = priority;
            this.task = task;
        }

        @Override
        public void run() {
   
   
            task.run();
        }

        @Override
        public int compareTo(PriorityTask other) {
   
   
            // 优先级高的任务应该排在前面(数字越小优先级越大)
            return Integer.compare(this.priority, other.priority);
        }
    }
}

以上程序执行结果如下:
image.png
从上述结果可以看出,线程池是完全按照优先级从高到低的顺序执行的(数字越小优先级越高),如果将 compareTo 中的排序方法倒置之后,那么线程池的执行顺序就完全相反了,可见使用 PriorityBlockingQueue 实现优先级线程池的效果非常显著。

课后思考

那么问题来了,PriorityBlockingQueue 在并发环境下会有线程安全问题吗?PriorityBlockingQueue 底层是如何保证线程安全的?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

相关文章
|
9月前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
9月前
|
安全 Java 程序员
面试必看:如何设计一个可以优雅停止的线程?
嘿,大家好!我是小米。今天分享一篇关于“如何停止一个正在运行的线程”的面试干货。通过一次Java面试经历,我明白了停止线程不仅仅是技术问题,更是设计问题。Thread.stop()已被弃用,推荐使用Thread.interrupt()、标志位或ExecutorService来优雅地停止线程,避免资源泄漏和数据不一致。希望这篇文章能帮助你更好地理解Java多线程机制,面试顺利! 我是小米,喜欢分享技术的29岁程序员。欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
217 53
|
8月前
|
数据采集 Java Linux
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
124 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
|
8月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
479 14
|
8月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
132 13
|
8月前
|
缓存 安全 Java
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
243 6
|
9月前
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
233 11
|
10月前
|
并行计算 算法 安全
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
489 3
|
11月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
12月前
|
缓存 监控 算法
小米面试题:多级缓存一致性问题怎么解决
【10月更文挑战第23天】在现代分布式系统中,多级缓存架构因其能够显著提高系统性能和响应速度而被广泛应用。
738 3