1. 向线程传递参数
当你启动一个线程时,往往希望把一些“工作”交给它:文件名、数字范围或问候语。没有参数的线程就像没有地址的快递员:满城乱跑,却不知道把披萨送到哪里。
怎么做?
在 Java 中,创建线程通常有两种方式:
- 继承 Thread 类
- 实现 Runnable 接口(或使用 lambda 表达式)
参数传递通常通过构造函数(由实现 Runnable 的类提供)来完成。这种方式简单、安全、易懂。把字段声明为 final——就能保护参数不被其他线程修改。使用 setter/公共字段会带来同步问题。
示例 1:通过构造函数给线程传参
完善一个教学示例:在单独的线程中处理用户订单。
public class OrderProcessor implements Runnable {
private final String orderId;
public OrderProcessor(String orderId) {
this.orderId = orderId;
}
@Override
public void run() {
System.out.println("正在处理订单: " + orderId + " 在线程 " + Thread.currentThread().getName());
// 这里可能是一个耗时的处理...
}
}
创建并启动线程:
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new OrderProcessor("ORDER-001"));
Thread thread2 = new Thread(new OrderProcessor("ORDER-002"));
thread1.start();
thread2.start();
}
}
预期输出:
正在处理订单: ORDER-001 在线程 Thread-0
正在处理订单: ORDER-002 在线程 Thread-1
示例 2:使用 lambda 表达式传参(Java 8+)
如果任务很简单,可以不单独写一个类。
public class Main {
public static void main(String[] args) {
String user1 = "瓦夏";
String user2 = "卡佳";
Thread thread1 = new Thread(() -> {
System.out.println("你好," + user1 + " 来自 " + Thread.currentThread().getName());
});
Thread thread2 = new Thread(() -> {
System.out.println("你好," + user2 + " 来自 " + Thread.currentThread().getName());
});
thread1.start();
thread2.start();
}
}
为什么不应使用 setter/getter 传参?
如果通过 setter 或公共字段传递参数,存在其他线程在执行期间修改其值的风险,这会导致难以捕捉的错误。最好把字段声明为 final 并通过构造函数传递。
2. 线程优先级
在 Java 中,每个线程都有一个优先级——从 1 到 10 的整数,用来告诉调度器(scheduler)这个线程相对于其他线程“有多重要”。默认所有线程的优先级是 5(Thread.NORM_PRIORITY)。
重要:优先级只是对操作系统的一个“提示”。它并不保证优先级为 10 的线程会比优先级为 1 的线程跑得更快。一切取决于操作系统、其配置以及当前负载。
如何为线程设置优先级?
Thread 类有这些方法:
- setPriority(int newPriority)
- getPriority()
以及三种标准常量:
- Thread.MIN_PRIORITY(1)
- Thread.NORM_PRIORITY(5)
- Thread.MAX_PRIORITY(10)
示例 3:为线程设置优先级
public class PriorityDemo {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("线程 " + Thread.currentThread().getName() +
" 的优先级为 " + Thread.currentThread().getPriority());
};
Thread low = new Thread(task, "LowPriority");
Thread norm = new Thread(task, "NormalPriority");
Thread high = new Thread(task, "HighPriority");
low.setPriority(Thread.MIN_PRIORITY); // 1
norm.setPriority(Thread.NORM_PRIORITY); // 5
high.setPriority(Thread.MAX_PRIORITY); // 10
low.start();
norm.start();
high.start();
}
}
预期输出(行的顺序不保证一致!):
线程 LowPriority 的优先级为 1
线程 HighPriority 的优先级为 10
线程 NormalPriority 的优先级为 5
优先级会影响执行顺序吗?
大多数情况下——不会。优先级可能影响处理器分配给线程的时间,但并不保证启动或结束的顺序。不要把程序逻辑建立在优先级之上——把它当作“软”提示,例如让后台线程不干扰主线程。
表:线程优先级常量
| 常量 | 值 | 说明 |
|---|---|---|
|
1 | 最低优先级 |
|
5 | 普通优先级 |
|
10 | 最高优先级 |
3. 线程命名
线程名就是并发世界里的“昵称”。当你有几十个线程时,如果日志里不是 "Thread-7",而是 "FileUploader-1",调试会轻松很多。这对分析日志和定位错误尤其重要。
如何给线程命名?
可以在 Thread 的构造函数中直接指定,也可以通过 setName 方法设置。使用 getName 获取当前名称。
Thread t = new Thread(() -> {
System.out.println("我在工作!");
}, "MyThreadName");
t.start();
示例 4:具名线程
public class NamedThreads {
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
System.out.println("我是线程:" + Thread.currentThread().getName());
}, "Downloader");
Thread threadB = new Thread(() -> {
System.out.println("我是线程:" + Thread.currentThread().getName());
});
threadB.setName("Uploader");
threadA.start();
threadB.start();
}
}
预期输出:
我是线程:Downloader
我是线程:Uploader
4. 实践:多个线程,含不同参数、优先级与名称
把一切组合到一个示例:每个订单都是一个独立线程,拥有自己的名称和优先级。
public class OrderProcessor implements Runnable {
private final String orderId;
private final int processingTimeMs;
public OrderProcessor(String orderId, int processingTimeMs) {
this.orderId = orderId;
this.processingTimeMs = processingTimeMs;
}
@Override
public void run() {
System.out.println("[" + Thread.currentThread().getName() + "] 开始处理订单 " + orderId +
"(优先级 " + Thread.currentThread().getPriority() + ")");
try {
Thread.sleep(processingTimeMs); // 模拟工作
} catch (InterruptedException e) {
System.out.println("[" + Thread.currentThread().getName() + "] 被中断!");
}
System.out.println("[" + Thread.currentThread().getName() + "] 完成处理订单 " + orderId);
}
public static void main(String[] args) {
Thread fastOrder = new Thread(new OrderProcessor("FAST-ORDER", 500), "FastOrderThread");
Thread normalOrder = new Thread(new OrderProcessor("NORMAL-ORDER", 1000), "NormalOrderThread");
Thread slowOrder = new Thread(new OrderProcessor("SLOW-ORDER", 2000), "SlowOrderThread");
fastOrder.setPriority(Thread.MAX_PRIORITY);
normalOrder.setPriority(Thread.NORM_PRIORITY);
slowOrder.setPriority(Thread.MIN_PRIORITY);
fastOrder.start();
normalOrder.start();
slowOrder.start();
}
}
你将看到:
- 每个线程都会打印它正在处理的订单、自己的名称和优先级。
- 处理时间通过 Thread.sleep 模拟。
- 线程的完成顺序可能与优先级并不一致。
5. 重要注意事项与要点
参数传递:只通过构造函数!
这样更安全。把参数声明为 final——创建对象后就无法再修改,其他线程也不能“塞”进自己的值。这对避免数据竞争至关重要。
优先级:不要基于它构建业务逻辑
优先级就像对服务员说:“能否快点上咖啡?”有时奏效,有时不行。它是对操作系统的建议,而不是执行规则。
线程名:用于调试
能在分析日志时大幅节省时间。养成一开始就设置有意义名称的习惯。
6. 在处理线程参数与优先级时的常见错误
错误 1:通过公共字段传参。 如果把参数声明为普通字段并在启动线程后修改,可能会得到不可预测的结果。请使用 final 字段和构造函数。
错误 2:指望优先级能保证顺序。 设为 Thread.MAX_PRIORITY 就以为线程一定第一个?不是的。不要用优先级来做同步或驱动业务逻辑。
错误 3:日志里都是未命名的线程。 当日志里只有 “Thread-3”、“Thread-7” 时,很难找到问题根源。请用构造函数或 setName 给线程起有意义的名字。
错误 4:多个线程复用同一个 Runnable 对象。 如果 Runnable 对象保存了状态,而你把它传给多个线程,那么它们会“共享”参数——这是导致数据竞争的捷径。为每个线程创建独立的 Runnable。
错误 5:通过 set 方法传参。 如果在对象创建/线程启动之后再通过 set* 方法设置参数,值可能在运行中被改变——典型的 race condition。
GO TO FULL VERSION