小米面試:如何實(shí)現(xiàn)優(yōu)先級(jí)線程池?
我們知道,線程池中的所有線程都是由統(tǒng)一的線程工廠來(lái)創(chuàng)建的,當(dāng)我們指定線程工廠時(shí),線程池中的所有線程會(huì)使用我們指定的線程工廠來(lái)創(chuàng)建線程;但如果沒(méi)有指定線程工廠,則會(huì)使用默認(rèn)的線程工廠 DefaultThreadFactory 來(lái)創(chuàng)建線程,核心源碼如下:
DefaultThreadFactory() {
@SuppressWarnings("removal")
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
那么問(wèn)題來(lái)了,面試官問(wèn)的是“如何實(shí)現(xiàn)優(yōu)先級(jí)線程池?”,為什么我們一上來(lái)先講了線程工廠呢?
這是因?yàn)椋?dāng)我們講到線程池優(yōu)先級(jí)的時(shí)候,我們首先會(huì)想到線程的優(yōu)先級(jí),所以按照慣性思考,當(dāng)面試官問(wèn)到如何使用實(shí)現(xiàn)優(yōu)先級(jí)線程池時(shí),我們首先會(huì)考慮是不是在創(chuàng)建線程池的時(shí)候,可以通過(guò)某種方法來(lái)創(chuàng)建不同的線程優(yōu)先級(jí),從而實(shí)現(xiàn)優(yōu)先級(jí)線程池?這就是開頭我們一上來(lái)就講線程工廠的原因。
那在線程工廠中如何設(shè)置線程的優(yōu)先級(jí)呢?
它的設(shè)置也比較簡(jiǎn)單,如下代碼所示:
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();
// 創(chuàng)建線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
threadFactory);
// 提交任務(wù)
executor.execute(() -> System.out.println("Task 1"));
executor.execute(() -> System.out.println("Task 2"));
// 關(guān)閉線程池
executor.shutdown();
}
static class CustomThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
// 設(shè)置線程優(yōu)先級(jí)為最低優(yōu)先級(jí)
thread.setPriority(Thread.MIN_PRIORITY);
return thread;
}
}
}
但是這種方式也有問(wèn)題,那就是線程工廠是統(tǒng)一的,所以即使能在線程工廠中設(shè)置線程的優(yōu)先級(jí),那么也是將整個(gè)線程池中的所有線程都設(shè)置成統(tǒng)一的優(yōu)先級(jí)了,而不能解決咱們本文提出的問(wèn)題的,那如何才能實(shí)現(xiàn)優(yōu)先級(jí)線程池呢?
1.優(yōu)先級(jí)線程池實(shí)現(xiàn)思路
轉(zhuǎn)念一想,既然不能在線程優(yōu)先級(jí)上下功夫,但我們是否可以在線程池的任務(wù)隊(duì)列上動(dòng)點(diǎn)心思呢?
此時(shí)我們想到,可以使用 PriorityBlockingQueue 優(yōu)先級(jí)隊(duì)列來(lái)對(duì)任務(wù)進(jìn)行排序啊(PriorityBlockingQueue 天生支持按照優(yōu)先級(jí)自動(dòng)排序任務(wù)的),這樣不就能保證優(yōu)先級(jí)高的任務(wù)會(huì)被線程池優(yōu)先獲取并執(zhí)行了嘛!
所以,有時(shí)候一條路走不通的時(shí)候,我們可以嘗試換一個(gè)思路再試試。
2.優(yōu)先級(jí)隊(duì)列使用
我們先來(lái)測(cè)試一下 PriorityBlockingQueue 的使用,以嘗試其可行性,示例代碼如下:
import java.util.concurrent.PriorityBlockingQueue;
public class PriorityBlockingQueueExample {
public static void main(String[] args) {
PriorityBlockingQueue<Task> priorityQueue = new PriorityBlockingQueue<>();
// 添加任務(wù)到優(yōu)先級(jí)隊(duì)列
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));
// 從優(yōu)先級(jí)隊(duì)列中取出任務(wù)并執(zhí)行
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);
}
}
}
以上程序的執(zhí)行結(jié)果如下:
從上述結(jié)果和代碼可以看出,我們添加任務(wù)的順序是:1、4、3、2,但最終會(huì)按照優(yōu)先級(jí)排隊(duì)執(zhí)行的順序是:1、2、3、4,執(zhí)行結(jié)果符合我們的預(yù)期,優(yōu)先級(jí)高的任務(wù)先被執(zhí)行了(數(shù)字越小,優(yōu)先級(jí)越高)。
3.優(yōu)先級(jí)線程池
因此,我們實(shí)現(xiàn)的優(yōu)先級(jí)線程池的最終代碼如下:
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("優(yōu)先級(jí):" + 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) {
// 優(yōu)先級(jí)高的任務(wù)應(yīng)該排在前面(數(shù)字越小優(yōu)先級(jí)越大)
return Integer.compare(this.priority, other.priority);
}
}
}
以上程序執(zhí)行結(jié)果如下:
從上述結(jié)果可以看出,線程池是完全按照優(yōu)先級(jí)從高到低的順序執(zhí)行的(數(shù)字越小優(yōu)先級(jí)越高),如果將 compareTo 中的排序方法倒置之后,那么線程池的執(zhí)行順序就完全相反了,可見使用 PriorityBlockingQueue 實(shí)現(xiàn)優(yōu)先級(jí)線程池的效果非常顯著。