CodeGym /课程 /JAVA 25 SELF /线程参数与优先级

线程参数与优先级

JAVA 25 SELF
第 51 级 , 课程 3
可用

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 中,每个线程都有一个优先级——从 110 的整数,用来告诉调度器(scheduler)这个线程相对于其他线程“有多重要”。默认所有线程的优先级是 5Thread.NORM_PRIORITY)。

重要:优先级只是对操作系统的一个“提示”。它并不保证优先级为 10 的线程会比优先级为 1 的线程跑得更快。一切取决于操作系统、其配置以及当前负载。

如何为线程设置优先级?

Thread 类有这些方法:

  • setPriority(int newPriority)
  • getPriority()

以及三种标准常量:

  • Thread.MIN_PRIORITY1
  • Thread.NORM_PRIORITY5
  • Thread.MAX_PRIORITY10

示例 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

优先级会影响执行顺序吗?

大多数情况下——不会。优先级可能影响处理器分配给线程的时间,但并不保证启动或结束的顺序。不要把程序逻辑建立在优先级之上——把它当作“软”提示,例如让后台线程不干扰主线程。

表:线程优先级常量

常量 说明
Thread.MIN_PRIORITY
1 最低优先级
Thread.NORM_PRIORITY
5 普通优先级
Thread.MAX_PRIORITY
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

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION