首页 > 编程笔记 > Java笔记 阅读:43

Java创建线程的3种方式(非常详细,附带实例)

Java 使用 Thread 类代表线程,所有的线程对象都必须是 Thread 类或其子类的实例,每个线程的作用是完成一项任务,实际上就是执行一段程序流(一段顺序执行的代码)。

在 Java 中创建线程有 3 种方式,分别是:

1、继承Thread类创建线程

Thread 是 Java 中代表线程的类,位于 java.lang 包中。

Thread 类中包含封装线程具体信息的属性(如线程的名称、id 和优先级等),以及对线程进行操作的常用方法(如线程休眠和唤醒等)。在 Java 中,每个 Thread 类的对象代表一个具体的线程。

Thread 类常用的构造方法如下:
Thread 类其他常用的方法如下:
使用 Thread 类实现线程的步骤如下:
  1. 定义 Thread 类的子类并重写该类的 run() 方法,该方法的方法体代表该线程需要完成的任务;
  2. 创建 Thread 类的实例,即创建线程对象;
  3. 调用线程的 start() 方法来启动线程。

【实例】通过继承 Thread 类来创建线程。
public class Demo {
    public static void main(String[] args) {
        // 基于自定义线程类创建线程对象
        MyThread t1 = new MyThread("Thread1");
        // 启动线程
        t1.start();
        MyThread t2 = new MyThread("Thread2");
        t2.start();
    }
}

/**
* 自定义线程类
*/
class MyThread extends Thread {
    public MyThread(String name) {
        // 调用父类的构造方法,初始化线程名称
        super(name);
        // 调用父类的getName()方法,获取线程名称
        System.out.println("Creating " + this.getName());
    }

    @Override
    public void run() {
        System.out.println("Running " + this.getName());
        try {
            for (int i = 3; i > 0; i--) {
                System.out.println("Thread: " + this.getName() + ", " + i);
                // 让线程睡眠一段时间
                Thread.sleep(50);
            }
        } catch (InterruptedException e) {
            System.out.println("Thread " + this.getName() + " interrupted.");
        }
        System.out.println("Thread " + this.getName() + " exiting.");
    }

    @Override
    public void start() {
        System.out.println("Starting " + this.getName());
        // 调用Thread类的start()方法,启动线程
        super.start();
    }
}
运行结果为:

Creating Thread1
Starting Thread1
Creating Thread2
Starting Thread2
Running Thread1
Thread: Thread1, 3
Running Thread2
Thread: Thread2, 3
Thread: Thread2, 2
Thread: Thread1, 2
Thread: Thread2, 1
Thread: Thread1, 1
Thread Thread2 exiting.
Thread Thread1 exiting.

可以看出,main() 方法是程序的主入口,用于创建并启动子线程。线程是交互执行的。

2、实现Runnable接口创建线程

Runnable 接口用来封装一个线程启动后所要执行的具体逻辑。在 Java 中,任何打算由线程执行的类,都应该实现 Runnable 接口,并提供其中定义的抽象方法 run() 的具体逻辑。实际上,Thread 类也实现了 Runnable 接口。

Runnable 接口的设计,实现了线程管理和线程执行逻辑的分离。Thread 类负责线程属性的封装和线程状态的管理。Runnable 接口的实现类负责提供线程的具体执行逻辑。

使用 Runnable 接口创建线程的步骤如下:
  1. 定义 Runnable 接口的实现类,并且重写它的 run() 方法,这个方法同样是该线程的执行体;
  2. 创建 Runnable 实现类的实例,并且将此实例作为 Thread 类的 target 创建一个 Thread 对象,该对象才是真正的线程对象;
  3. 调用 start() 方法启动该线程。

【实例】使用 Runnable 接口创建线程类。
public class Demo {
    public static void main(String[] args) {
        // 创建Runnable接口实现类的对象
        MyRunner myRunner = new MyRunner();
        // 创建子线程对象,绑定Runnable接口实现类的对象
        Thread t1 = new Thread(myRunner, "Thread1");
        Thread t2 = new Thread(myRunner, "Thread2");
        // 启动子线程对象
        t1.start();
        t2.start();
    }
}

class MyRunner implements Runnable {
    @Override
    public void run() {
        // 获取当前正在执行的线程对象的名称
        String threadName = Thread.currentThread().getName();
        System.out.println("Running " + threadName);
        try {
            for (int i = 3; i > 0; i--) {
               System.out.println("Thread: " + threadName + ", " + i);
               // 让线程睡眠一段时间
               Thread.sleep(50);
           }
        } catch (InterruptedException e) {
            System.out.println("Thread " + threadName + " interrupted.");
        }
        System.out.println("Thread " + threadName + " exiting.");
    }
}
运行结果为:

Running Thread1
Thread: Thread1, 3
Running Thread2
Thread: Thread2, 3
Thread: Thread1, 2
Thread: Thread2, 2
Thread: Thread1, 1
Thread: Thread2, 1
Thread Thread2 exiting.
Thread Thread1 exiting.

3、Callable和Future接口创建线程

通过 Thread 类和 Runnable 接口创建多线程,需要重写 run() 方法,但该方法没有返回值,因此无法从多个线程中获取返回结果。Java 提供了一个 Callable 接口,既可以通过该接口创建多线程,又可以提供返回值。

Callable 是 JDK 1.5 推出的接口,与 Runnable 接口相似,其中只有一个方法 call()。call() 方法可以抛出 Exception 类及其子类的异常,并且可以返回指定的泛型类对象。

FutureTask 类代表一个可取消的异步计算任务。FutureTask 是 Future 接口的基础实现类,包含启动异步和取消计算的方法、查看计算任务是否完成的方法和查询计算结果的方法。只有在计算完成后才能检索结果,如果计算尚未完成,那么 get() 方法将被阻塞。一旦计算完成,就不能重新开始或取消计算(除非使用 runAndReset 调用计算)。

通过 Callable 接口和 Future 接口创建线程的步骤如下:
  1. 创建 Callable 接口的实现类,同时实现 call() 方法,该方法将作为线程执行体,并且有返回值;
  2. 创建 Callable 接口的实现类的实例,使用 FutureTask 类来包装 Callable 对象,FutureTask 对象封装了 Callable 对象的 call() 方法的返回值;
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程;
  4. 调用 FutureTask 对象的 get() 方法获得子线程执行结束后的返回值。

【实例】使用 Callable 接口创建线程。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Demo {
    public static void main(String[] args) {
        CallableDemo cd = new CallableDemo();
        FutureTask<Integer> ft = new FutureTask<>(cd);
        new Thread(ft, "有返回值的线程").start();
       
        try {
           System.out.println("子线程的返回值:" + ft.get());
       } catch (InterruptedException e) {
           e.printStackTrace();
        } catch (Exception e) {
           e.printStackTrace();
        }
    }
}

class CallableDemo implements Callable<Integer> {
    public Integer call() throws Exception {
        int i = 0;
        for (; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        return i;
    }
}
运行结果为:

有返回值的线程 0
有返回值的线程 1
有返回值的线程 2
子线程的返回值:3

可以看到,调用 FutureTask 对象的 get() 方法返回的是 call() 方法运行完成后得到的结果。

3种线程创建方式的对比

当采用继承 Thread 类的方式创建线程类时,受 Java 单重继承机制的影响,该线程类无法继承其他类,可能对该线程类的扩展性造成影响。

当使用 Runnable 接口和 Callable 接口创建线程类时,线程类不仅实现了 Runnable 接口和 Callable 接口,还可以继承其他类,扩展性更好。

在大多数情况下,如果只打算覆盖 run() 方法而不打算覆盖 Thread 类其他的方法,那么应该使用 Runnable 接口。这很重要,因为除非开发者打算修改或增强类的基本行为,否则不应将类子类化。

当采用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,就无须使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

相关文章