ZetCode

Java 线程

上次修改时间:2024 年 7 月 13 日

在本文中,我们将介绍 Java 线程,并列出它们的优点和缺点。我们还将提到在使用线程时会出现哪些问题。我们将在几个示例中展示如何创建简单的线程。

Java 线程是程序中一个轻量级的执行单元。它允许程序表面上同时执行多个任务。Java 虚拟机允许应用程序同时运行多个执行线程。

每个 Java 程序至少有一个线程,称为主线程。当程序启动时,Java 虚拟机创建此线程,并调用 main 方法。

使用 Java 线程的优点是:

Java 线程的常见用例包括:

由于线程并发运行,代码执行的顺序是不可预测的。 当线程读取和写入相同的变量时,可能会导致并发问题。为了避免这种情况,应尽可能减少线程之间共享的属性,并使用同步技术。

多线程问题

多线程程序本质上比单线程程序更复杂。这可能会导致难以识别和重现的错误。

常见的并发问题包括:

同步

线程同步是控制多个线程对共享资源访问的能力。它确保一次只有一个线程可以访问代码的关键部分或共享资源。

同步技术包括:

请注意,通过同步对共享资源的访问来确保线程安全可能会引入开销。与单线程方法相比,这会减慢我们的程序。

创建线程

在 Java 中创建线程有两种主要方法:a) 扩展 Thread 类或 b) 实现 Runnable 接口。

Main.java
class Worker implements Runnable {

    @Override
    public void run() {
        System.out.println("worker is running");
    }
}


void main() {

    System.out.println("main thread started");

    var myRunnable = new Worker();
    var thread = new Thread(myRunnable);

    thread.start();

    System.out.println("main thread ended");
}

在这里,我们创建一个实现 Runnable 接口的 Worker 类。Runnable 接口只有一个方法,run,它定义了线程要执行的代码。线程通过 start 方法启动。

在第二个示例中,Worker 类扩展了 Thread 类。

Main.java
class Worker extends Thread {


    @Override
    public void run() {
        System.out.println("worker is running");
    }
}


void main() {

    System.out.println("main thread started");

    var myRunnable = new Worker();
    var thread = new Thread(myRunnable);

    thread.start();

    System.out.println("main thread ended");
}

在该示例中,我们从 Thread 类扩展并覆盖 run 方法。run 方法定义了线程要执行的代码。

Thread.sleep

Thread.sleep(ms) 方法用于暂停当前线程的执行,暂停时间以毫秒为单位。

Main.java
class Task implements Runnable {

    private int delay;
    private String name;

    public Task(String name, int delay) {

        this.name = name;
        this.delay = delay;
    }

    @Override
    public void run() {

        try {
            System.out.printf("starting task %s%n", this.name);
            Thread.sleep(delay);
            System.out.printf("finishing task %s%n", this.name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


void main() {

    var task1 = new Task("Task 2000", 2000);
    var t1 = new Thread(task1);
    t1.start();

    var task2 = new Task("Task 1000", 1000);
    var t2 = new Thread(task2);
    t2.start();

    var task3 = new Task("Task 500", 500);
    var t3 = new Thread(task3);
    t3.start();

    System.out.println("tasks launched");
}

实现 Task 接口的类会休眠指定的毫秒数。我们在主应用程序线程中启动三个线程。它们分别休眠 2 秒、1 秒和 500 毫秒。

$ java Main.java
tasks launched
starting task Task 1000
starting task Task 2000
starting task Task 500
finishing task Task 500
finishing task Task 1000
finishing task Task 2000

首先,主程序线程完成。这三个线程以不可预测的顺序启动。它们稍后根据其休眠值完成。

Thread join 方法

Threadjoin 方法用于同步线程的执行。

该方法的使用场景

Main.java
class Worker extends Thread {

    private int delay;
    private String msg;

    public Worker(int delay, String msg) {

        this.delay = delay;
        this.msg = msg;
    }

    public void run() {

        try {
            Thread.sleep(delay);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(msg);
    }
}


void main() {

    var w1 = new Worker(2000, "Hello there");
    var w2 = new Worker(1000, "New mail received");
    var w3 = new Worker(500, "Notes taken");

    // start three threads
    w1.start();
    w2.start();
    w3.start();

    // wait for threads to end
    try {

        // join is a blocker method which waits for a thread to complete.

        // the w1.join() causes the current (main) thread to pause execution
        // until w1's thread terminates.
        w1.join();
        w2.join();
        w3.join();

    } catch (InterruptedException e) {

        e.printStackTrace();
    }

    System.out.println("finished tasks");
}

在该程序中,我们启动了三个额外的线程。使用 join 方法,我们暂停主线程的执行,暂停时间为这三个线程的持续时间。

$ java Main.java
Notes taken
New mail received
Hello there
finished tasks

在三个线程结束其任务后,才会显示 finished tasks

SwingWorker

SwingWorker 专门用于在与 Swing 事件分派线程 (EDT) 分开的单独线程中运行长时间运行的任务。它简化了在后台执行长时间运行的任务,同时保持 EDT 的响应性。

长时间运行的任务被放置在 doInBackground 方法中。此方法在单独的线程上运行,从而使 EDT 可以处理 UI 更新。

com/zetcode/ButtonTaskEx.java
package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import java.awt.EventQueue;

class MyWorker extends SwingWorker<Void, Void> {

    @Override
    protected Void doInBackground() throws Exception {
        // Simulate a time-consuming task
        Thread.sleep(3000);
        return null;
    }

    @Override
    protected void done() {
        System.out.println("task done");
    }
}

public class ButtonTaskEx extends JFrame {

    public ButtonTaskEx() {

        initUI();
    }

    private void initUI() {

        var taskButton = new JButton("Task");

        taskButton.addActionListener((event) -> {
            var worker = new MyWorker();
            worker.execute();
        });


    //    taskButton.addActionListener((event) -> {
    //        try {
    //            Thread.sleep(3000);
    //           System.out.println("task done");
    //        } catch (InterruptedException e) {
    //            throw new RuntimeException(e);
    //        }
    //    });

        createLayout(taskButton);

        setTitle("Task button");
        setSize(500, 450);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
        );
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ButtonTaskEx();
            ex.setVisible(true);
        });
    }
}

在该程序中,我们有一个按钮,可以启动一个 3 秒的后台任务。如果我们将任务放置在 SwingWorker 中,则应用程序将保持响应。否则,应用程序将在任务持续时间内冻结。

来源

Java 线程 - 参考

在本文中,我们定义了 Java 线程,并提供了一些基本的代码示例。

作者

我叫 Jan Bodnar,是一名充满激情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。到目前为止,我已经撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有Java教程