前言
哎呀,这种情况你肯定遇到过吧!
正在家里忙活着,突然------咚咚咚有人敲门,咕噜咕噜开水开了,铃铃铃电话响了,哇哇哇孩子又哭了...
我去,四件事一起来,人都懵了!你说先搞哪个?
其实这跟我们写Java多线程程序是一个道理。CPU就像你一样,同时来了好几个任务(线程),它也得决定先处理哪个。这就是今天要聊的线程优先级问题。
生活场景 vs 多线程概念对比图
css
生活场景 多线程概念
┌─────────────┐ ┌─────────────┐
│ 你 │ ≈ │ CPU │
│ (处理者) │ │ (处理器) │
└─────────────┘ └─────────────┘
↑ ↑
│ 处理 │ 执行
↓ ↓
┌─────────────┐ ┌─────────────┐
│ 孩子哭了 │ ≈ │ 线程A │
│ 开水开了 │ │ 线程B │
│ 电话响了 │ │ 线程C │
│ 有人敲门 │ │ 线程D │
└─────────────┘ └─────────────┘
一、先分析分析这四件事的轻重缓急
哪个最急?
咱们用程序员的脑子想想这四件事:
孩子哭了 - 这个最急!小孩哭肯定有原因,饿了?尿了?还是哪里不舒服?万一有危险呢,必须第一时间处理。
开水开了 - 这个也挺急的,继续烧下去水都蒸干了,还费电费气,搞不好还有安全隐患。
电话响了 - 嗯,可能是重要的事,但一般不会有生命危险,可以稍等一下。
有人敲门 - 这个最不急,大不了让人家多等一会儿,又不会出什么大事。
用代码表示就是这样:
java
// 孩子哭了 - 最高优先级,安全第一嘛
class CryingChildTask implements Runnable {
public void run() {
System.out.println("🍼 赶紧看看孩子怎么了");
}
}
// 开水开了 - 也挺急的,别浪费了
class BoilingWaterTask implements Runnable {
public void run() {
System.out.println("🔥 快去关火,别把水烧干了");
}
}
// 电话响了 - 中等优先级,可能有事
class PhoneRingingTask implements Runnable {
public void run() {
System.out.println("📞 接个电话看看啥事");
}
}
// 有人敲门 - 不急,让等等
class DoorKnockingTask implements Runnable {
public void run() {
System.out.println("🚪 开门,不好意思让你久等了");
}
}
给这些事情排个优先级
Java里面线程优先级是1到10,咱们按紧急程度来分:
- 孩子哭了:优先级10 - 最高级别,安全第一
- 开水开了:优先级7 - 比较急,但没孩子重要
- 电话响了:优先级5 - 中等,可能重要但不紧急
- 有人敲门:优先级3 - 最低,让人等等也没事
线程优先级分配图
scss
优先级等级 任务类型 生活场景 执行顺序
┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────┐
│ 10 │ │ 线程A │ │ 孩子哭了 │ │ 1st │
│(最高) │ │(紧急) │ │ 🍼 │ │ 优先 │
└─────────┘ └─────────┘ └─────────────┘ └─────────┘
┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────┐
│ 7 │ │ 线程B │ │ 开水开了 │ │ 2nd │
│(较高) │ │(重要) │ │ 🔥 │ │ 优先 │
└─────────┘ └─────────┘ └─────────────┘ └─────────┘
┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────┐
│ 5 │ │ 线程C │ │ 电话响了 │ │ 3rd │
│(普通) │ │(一般) │ │ 📞 │ │ 优先 │
└─────────┘ └─────────┘ └─────────────┘ └─────────┘
┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────┐
│ 3 │ │ 线程D │ │ 有人敲门 │ │ 4th │
│(较低) │ │(可等) │ │ 🚪 │ │ 优先 │
└─────────┘ └─────────┘ └─────────────┘ └─────────┘
二、Java多线程优先级到底怎么玩?
Thread.setPriority()这个方法
好了,现在我们知道了优先级的概念,那在Java里怎么设置呢?很简单,用Thread.setPriority()
方法就行了。
java
public class RealLifeThreadDemo {
public static void main(String[] args) {
// 创建四个线程,就像生活中的四件事
Thread childThread = new Thread(new CryingChildTask(), "孩子哭泣");
Thread waterThread = new Thread(new BoilingWaterTask(), "开水沸腾");
Thread phoneThread = new Thread(new PhoneRingingTask(), "电话响铃");
Thread doorThread = new Thread(new DoorKnockingTask(), "有人敲门");
// 设置优先级,数字越大越优先
childThread.setPriority(Thread.MAX_PRIORITY); // 10,最高
waterThread.setPriority(7); // 7,比较高
phoneThread.setPriority(Thread.NORM_PRIORITY); // 5,普通
doorThread.setPriority(3); // 3,比较低
System.out.println("好家伙,四件事同时来了!");
// 同时启动所有线程
childThread.start();
waterThread.start();
phoneThread.start();
doorThread.start();
// 等所有事情处理完
try {
childThread.join();
waterThread.join();
phoneThread.join();
doorThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("终于都搞定了!");
}
}
但是要注意!
这里有个坑,线程优先级只是个"建议",不是绝对的!就像你想先处理孩子哭泣,但如果你正在关开水,也不能立马丢下不管对吧?
JVM会尽量让高优先级的线程先执行,但不保证100%按顺序来。这跟操作系统的调度策略有关系。
来个更真实的例子
java
public class PriorityTestDemo {
private static boolean keepRunning = true;
static class TaskRunner implements Runnable {
private String taskName;
private int priority;
private int count = 0;
public TaskRunner(String taskName, int priority) {
this.taskName = taskName;
this.priority = priority;
}
public void run() {
Thread.currentThread().setPriority(priority);
while (keepRunning) {
count++;
if (count % 50000 == 0) {
System.out.println(taskName + " (优先级:" + priority + ") 执行了 " + count + " 次");
}
}
System.out.println("最终结果 - " + taskName + ": " + count + "次");
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("看看不同优先级的线程谁跑得快!\n");
Thread[] threads = {
new Thread(new TaskRunner("孩子哭泣处理", Thread.MAX_PRIORITY)),
new Thread(new TaskRunner("开水处理", 7)),
new Thread(new TaskRunner("电话处理", Thread.NORM_PRIORITY)),
new Thread(new TaskRunner("开门处理", 3))
};
// 一起开始
for (Thread thread : threads) {
thread.start();
}
// 跑3秒钟
Thread.sleep(3000);
keepRunning = false;
// 等所有线程结束
for (Thread thread : threads) {
thread.join();
}
}
}
运行这个程序,你会发现高优先级的线程确实执行次数更多,但差别可能没你想象的那么大。
线程调度流程图
arduino
线程调度器 (Thread Scheduler)
┌─────────────┐
│ CPU │
│ 调度器 │
└─────────────┘
│
┌─────────────┼─────────────┐
│ │ │
检查优先级 分配时间片 执行线程
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 优先级队列 │ │ 时间片轮转 │ │ 线程执行 │
│ │ │ │ │ │
│ 高优先级 ──┐ │ │ 每个线程 │ │ 当前活跃 │
│ 中优先级 ──┼─┼─│ 获得CPU │ │ 线程运行 │
│ 低优先级 ──┘ │ │ 时间片 │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
└───────────── 循环 ─────────────┘
三、线程饥饿问题:可怜的敲门人
什么是线程饥饿?
想象一下,如果你家孩子特别爱哭,开水又老是开,电话还接个不停,那门外的人是不是要等到天荒地老?
这就是程序里的"线程饥饿"问题。低优先级的线程可能永远得不到执行机会,就像那个可怜的敲门人一样。
java
public class ThreadStarvationDemo {
private static final Object lock = new Object();
private static boolean keepRunning = true;
// 高优先级任务,霸占资源不放手
static class HighPriorityTask implements Runnable {
public void run() {
while (keepRunning) {
synchronized (lock) {
System.out.println("高优先级任务霸占资源中: " + Thread.currentThread().getName());
try {
Thread.sleep(100); // 故意占用时间长一点
} catch (InterruptedException e) {
break;
}
}
}
}
}
// 低优先级任务,可怜巴巴等资源
static class LowPriorityTask implements Runnable {
private int count = 0;
public void run() {
while (keepRunning) {
synchronized (lock) {
count++;
System.out.println("低优先级任务终于执行了! 第" + count + "次");
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("演示线程饥饿现象");
System.out.println("看看可怜的敲门人能不能得到关注\n");
// 创建几个高优先级线程(孩子哭、开水开、电话响)
Thread[] highThreads = {
new Thread(new HighPriorityTask(), "孩子哭泣"),
new Thread(new HighPriorityTask(), "开水沸腾"),
new Thread(new HighPriorityTask(), "电话响铃")
};
// 创建一个低优先级线程(敲门)
Thread lowThread = new Thread(new LowPriorityTask(), "有人敲门");
// 设置优先级
for (Thread thread : highThreads) {
thread.setPriority(Thread.MAX_PRIORITY);
}
lowThread.setPriority(Thread.MIN_PRIORITY);
// 启动所有线程
for (Thread thread : highThreads) {
thread.start();
}
lowThread.start();
// 跑3秒看效果
Thread.sleep(3000);
keepRunning = false;
// 等所有线程结束
for (Thread thread : highThreads) {
thread.join();
}
lowThread.join();
System.out.println("\n实验结束,看看敲门的人是不是被饿死了");
}
}
饥饿的危害
- 不公平:有些任务永远得不到执行机会
- 响应慢:用户体验差,系统看起来卡死了
- 资源浪费:线程创建了但不干活,白白占内存
线程饥饿现象图解
makefile
时间轴 ────────────────────────────────────────────────►
高优先级线程A: ████████████████████████████████████████
高优先级线程B: ████████████████████████████████████████
高优先级线程C: ████████████████████████████████████████
低优先级线程D: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
↑ ↑
开始等待 还在等待...
说明:
████ = 正在执行
░░░░ = 等待中(饥饿状态)
四、现代解决方案:线程池来救场
传统方式的问题
直接用Thread类有几个毛病:
- 创建线程开销大,就像每次有事都要重新雇人
- 线程数量难控制,容易把系统搞崩
- 优先级管理复杂,容易出现饥饿问题
线程池的好处
线程池就像是雇了一批固定的工人,有活就分配给他们干,没活就让他们待命。
线程池架构图
markdown
线程池管理架构
┌─────────────────────────────────────────────────┐
│ 应用程序 │
└─────────────────┬───────────────────────────────┘
│ 提交任务
▼
┌─────────────────────────────────────────────────┐
│ 任务分发器 │
│ 根据任务类型分配到不同线程池 │
└─────┬─────────────┬─────────────┬───────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│紧急线程池│ │普通线程池│ │后台线程池│
│ 2个线程 │ │ 2个线程 │ │ 1个线程 │
│优先级:10│ │优先级:5 │ │优先级:3 │
└─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│孩子哭泣 │ │接听电话 │ │开门迎客 │
│开水沸腾 │ │ │ │ │
└─────────┘ └─────────┘ └─────────┘
java
import java.util.concurrent.*;
public class ThreadPoolSolution {
// 不同优先级的线程池
private static final ExecutorService emergencyPool =
Executors.newFixedThreadPool(2, new PriorityThreadFactory("紧急", Thread.MAX_PRIORITY));
private static final ExecutorService normalPool =
Executors.newFixedThreadPool(2, new PriorityThreadFactory("普通", Thread.NORM_PRIORITY));
private static final ExecutorService lowPool =
Executors.newFixedThreadPool(1, new PriorityThreadFactory("低优先级", Thread.MIN_PRIORITY + 2));
// 自定义线程工厂
static class PriorityThreadFactory implements ThreadFactory {
private int threadNumber = 1;
private String namePrefix;
private int priority;
PriorityThreadFactory(String namePrefix, int priority) {
this.namePrefix = namePrefix;
this.priority = priority;
}
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, namePrefix + "-线程-" + threadNumber++);
thread.setPriority(priority);
return thread;
}
}
public static void handleLifeScenario() {
System.out.println("用线程池来处理生活中的并发场景\n");
// 紧急任务:孩子哭泣、开水沸腾
CompletableFuture<Void> childTask = CompletableFuture.runAsync(() -> {
System.out.println("🍼 [紧急线程池] 马上处理哭泣的孩子");
doWork("安抚孩子", 2000);
}, emergencyPool);
CompletableFuture<Void> waterTask = CompletableFuture.runAsync(() -> {
System.out.println("🔥 [紧急线程池] 马上关闭开水");
doWork("关闭开水", 1000);
}, emergencyPool);
// 普通任务:接电话
CompletableFuture<Void> phoneTask = CompletableFuture.runAsync(() -> {
System.out.println("📞 [普通线程池] 接听电话");
doWork("通话中", 3000);
}, normalPool);
// 低优先级任务:开门
CompletableFuture<Void> doorTask = CompletableFuture.runAsync(() -> {
System.out.println("🚪 [低优先级线程池] 开门迎客");
doWork("接待客人", 2500);
}, lowPool);
// 等所有任务完成
try {
CompletableFuture.allOf(childTask, waterTask, phoneTask, doorTask)
.get(10, TimeUnit.SECONDS);
System.out.println("\n✅ 所有事情都处理完了!");
} catch (Exception e) {
System.err.println("出错了: " + e.getMessage());
}
}
private static void doWork(String taskName, int duration) {
try {
System.out.println(" 正在: " + taskName + " (大概需要 " + duration + "ms)");
Thread.sleep(duration);
System.out.println(" ✓ " + taskName + " 搞定");
} catch (InterruptedException e) {
System.err.println(" ✗ " + taskName + " 被打断了");
}
}
public static void main(String[] args) {
handleLifeScenario();
// 关闭线程池
emergencyPool.shutdown();
normalPool.shutdown();
lowPool.shutdown();
}
}
更智能的调度方案
为了防止饥饿问题,我们可以搞个智能调度器,让等待时间长的任务优先级自动提升:
智能调度器工作流程图
scss
智能调度器 (防饥饿机制)
任务提交 ──► ┌─────────────┐ ──► 优先级队列
│ 任务包装器 │ (动态排序)
│ ┌─────────┐ │ │
│ │基础优先级│ │ │
│ │等待时间 │ │ ▼
│ │动态优先级│ │ ┌─────────┐
│ └─────────┘ │ │高优先级 │
└─────────────┘ │任务队列 │
└─────────┘
时间推移 │
┌─────────┐ │
│ +1秒 │ ──► 优先级 +1 ────────┘
│ +2秒 │ ──► 优先级 +2
│ +3秒 │ ──► 优先级 +3
└─────────┘
结果:等待越久的任务,优先级越高!
java
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
public class SmartScheduler {
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(4);
// 任务包装器,会根据等待时间自动提升优先级
static class SmartTask implements Comparable<SmartTask> {
private final Runnable task;
private final int basePriority;
private final long submitTime;
private final String name;
public SmartTask(Runnable task, int basePriority, String name) {
this.task = task;
this.basePriority = basePriority;
this.submitTime = System.currentTimeMillis();
this.name = name;
}
// 动态优先级:基础优先级 + 等待时间加成
public int getDynamicPriority() {
long waitTime = System.currentTimeMillis() - submitTime;
int bonus = (int) (waitTime / 1000); // 每等1秒加1点优先级
return Math.min(Thread.MAX_PRIORITY, basePriority + bonus);
}
public int compareTo(SmartTask other) {
return Integer.compare(other.getDynamicPriority(), this.getDynamicPriority());
}
public void run() {
long waitTime = System.currentTimeMillis() - submitTime;
System.out.printf("[%s] 开始执行 (动态优先级: %d, 等了: %dms)\n",
name, getDynamicPriority(), waitTime);
task.run();
}
}
private final PriorityBlockingQueue<SmartTask> taskQueue =
new PriorityBlockingQueue<>();
public SmartScheduler() {
// 每100ms检查一次任务队列
scheduler.scheduleWithFixedDelay(this::processTasks, 0, 100, TimeUnit.MILLISECONDS);
}
public void submitTask(Runnable task, int priority, String name) {
taskQueue.offer(new SmartTask(task, priority, name));
System.out.println("📝 提交任务: " + name + " (基础优先级: " + priority + ")");
}
private void processTasks() {
SmartTask task = taskQueue.poll();
if (task != null) {
task.run();
}
}
public void shutdown() {
scheduler.shutdown();
}
public static void main(String[] args) throws InterruptedException {
SmartScheduler scheduler = new SmartScheduler();
System.out.println("智能调度演示");
System.out.println("看看等待时间长的任务是不是能获得更高优先级\n");
// 提交任务
scheduler.submitTask(() -> doWork("孩子哭泣处理", 2000), 10, "孩子哭泣");
scheduler.submitTask(() -> doWork("开门迎客", 1500), 3, "开门迎客");
scheduler.submitTask(() -> doWork("开水处理", 1000), 7, "开水处理");
scheduler.submitTask(() -> doWork("电话接听", 2500), 5, "电话接听");
// 过一会再提交,看看优先级变化
Thread.sleep(2000);
scheduler.submitTask(() -> doWork("又有人敲门", 1000), 3, "第二次敲门");
Thread.sleep(8000); // 等任务执行完
scheduler.shutdown();
}
private static void doWork(String taskName, int duration) {
try {
System.out.println(" 🔄 " + taskName + " 执行中...");
Thread.sleep(duration);
System.out.println(" ✅ " + taskName + " 完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
五、实际开发中的经验
什么时候用线程优先级?
说实话,在实际项目中,我很少直接设置线程优先级。为啥?
- 不靠谱:不同平台表现不一样,Windows和Linux的调度策略差别挺大
- 难调试:优先级问题很难重现和调试
- 有更好的方案:线程池、消息队列这些工具更好用
更实用的做法
markdown
实际项目中的多线程架构
┌─────────────────────────────────────────────────┐
│ 应用层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │用户请求 │ │定时任务 │ │系统监控 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│ 线程池层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │关键任务池│ │普通任务池│ │后台任务池│ │
│ │ 2线程 │ │ 4线程 │ │ 1线程 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│ 队列层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │高优先级 │ │中优先级 │ │低优先级 │ │
│ │ 队列 │ │ 队列 │ │ 队列 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────┘
java
// 1. 用不同的线程池处理不同类型的任务
ExecutorService criticalPool = Executors.newFixedThreadPool(2); // 关键任务
ExecutorService normalPool = Executors.newFixedThreadPool(4); // 普通任务
ExecutorService backgroundPool = Executors.newFixedThreadPool(1); // 后台任务
// 2. 用队列控制任务执行顺序
BlockingQueue<Runnable> highPriorityQueue = new LinkedBlockingQueue<>();
BlockingQueue<Runnable> lowPriorityQueue = new LinkedBlockingQueue<>();
// 3. 用CompletableFuture组合异步任务
CompletableFuture.supplyAsync(() -> "处理孩子哭泣")
.thenCompose(result -> CompletableFuture.supplyAsync(() -> "关开水"))
.thenAccept(System.out::println);
避免常见坑
-
别滥用MAX_PRIORITY和MIN_PRIORITY
java// 不好的做法 thread.setPriority(Thread.MAX_PRIORITY); // 容易造成饥饿 // 好的做法 thread.setPriority(Thread.NORM_PRIORITY + 1); // 稍微提高一点就够了
-
别指望优先级解决所有问题
java// 不要这样想:"我把这个线程优先级设高点,就能保证先执行" // 应该这样想:"我用合适的工具来协调任务执行"
-
测试要在真实环境进行
java// 开发机上跑得好好的,生产环境可能完全不一样 // 多线程程序一定要在接近生产环境的地方测试
六、总结
回到最开始的问题:有人敲门,开水开了,电话响了,孩子哭了,你先顾谁?
生活中的智慧:
- 安全第一(孩子哭泣)
- 防患未然(关开水)
- 重要但不紧急的可以稍等(接电话)
- 不紧急的最后处理(开门)
编程中的对应:
- 系统关键任务用高优先级
- 用户交互任务中等优先级
- 后台任务低优先级
- 但别完全依赖优先级!
更好的解决方案:
- 用线程池管理线程
- 用队列控制任务顺序
- 用异步编程提高响应性
- 监控和调优系统性能
最重要的一点:多线程编程没有银弹,要根据具体场景选择合适的工具和策略。就像生活中处理多个并发事件一样,需要智慧、经验和灵活应变。
记住:好的程序员不是会用各种高深技术的人,而是能用最简单有效的方法解决问题的人。
多线程最佳实践总结图
markdown
多线程编程最佳实践
┌─────────────────────────────────────────────────┐
│ 设计原则 │
│ │
│ 简单 ──► 可靠 ──► 高效 ──► 可维护 │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ 易理解 少bug 性能好 好扩展 │
└─────────────────────────────────────────────────┘
│
┌─────────────────────────▼─────────────────────────┐
│ 工具选择 │
│ │
│ 线程池 ──► 队列 ──► 异步 ──► 监控 │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ 管理 排序 组合 调优 │
└─────────────────────────────────────────────────┘
│
┌─────────────────────────▼─────────────────────────┐
│ 避免陷阱 │
│ │
│ 饥饿 ──► 死锁 ──► 竞态 ──► 内存泄漏 │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ 公平 顺序 同步 清理 │
└─────────────────────────────────────────────────┘
写这篇文章的时候,我家孩子正好在哭,开水也开了,还真有人敲门...现实比代码更复杂啊!😅