Java创建线程的3种方式(非常详细,附带实例)
在 Java 中创建线程有 3 种方式,分别是:
- 通过定义 Thread 类的子类创建线程;
- 通过定义 Runnable 接口的实现类创建线程;
- 通过 Callable 接口和 Future 接口创建线程。
1、继承Thread类创建线程
Thread 是 Java 中代表线程的类,位于 java.lang 包中。Thread 类中包含封装线程具体信息的属性(如线程的名称、id 和优先级等),以及对线程进行操作的常用方法(如线程休眠和唤醒等)。在 Java 中,每个 Thread 类的对象代表一个具体的线程。
Thread 类常用的构造方法如下:
- public Thread():分配一个新的线程对象;
- public Thread(String name):分配一个指定名称的新的线程对象;
- public Thread(Runnable target):分配一个带指定目标的新的线程对象;
- public Thread(Runnable target, String name):分配一个带指定目标的且指定名称的新的线程对象。
Thread 类其他常用的方法如下:
- public String getName():获取当前线程名称;
- public void start():此线程开始执行,Java 虚拟机调用此线程的 run() 方法;
- public void run():此线程要执行的任务在此处定义代码;
- public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行);
- public static Thread currentThread():返回对当前正在执行的线程对象的引用。
使用 Thread 类实现线程的步骤如下:
- 定义 Thread 类的子类并重写该类的 run() 方法,该方法的方法体代表该线程需要完成的任务;
- 创建 Thread 类的实例,即创建线程对象;
- 调用线程的 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.
2、实现Runnable接口创建线程
Runnable 接口用来封装一个线程启动后所要执行的具体逻辑。在 Java 中,任何打算由线程执行的类,都应该实现 Runnable 接口,并提供其中定义的抽象方法 run() 的具体逻辑。实际上,Thread 类也实现了 Runnable 接口。Runnable 接口的设计,实现了线程管理和线程执行逻辑的分离。Thread 类负责线程属性的封装和线程状态的管理。Runnable 接口的实现类负责提供线程的具体执行逻辑。
使用 Runnable 接口创建线程的步骤如下:
- 定义 Runnable 接口的实现类,并且重写它的 run() 方法,这个方法同样是该线程的执行体;
- 创建 Runnable 实现类的实例,并且将此实例作为 Thread 类的 target 创建一个 Thread 对象,该对象才是真正的线程对象;
- 调用 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 接口创建线程的步骤如下:
- 创建 Callable 接口的实现类,同时实现 call() 方法,该方法将作为线程执行体,并且有返回值;
- 创建 Callable 接口的实现类的实例,使用 FutureTask 类来包装 Callable 对象,FutureTask 对象封装了 Callable 对象的 call() 方法的返回值;
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程;
- 调用 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
3种线程创建方式的对比
当采用继承 Thread 类的方式创建线程类时,受 Java 单重继承机制的影响,该线程类无法继承其他类,可能对该线程类的扩展性造成影响。当使用 Runnable 接口和 Callable 接口创建线程类时,线程类不仅实现了 Runnable 接口和 Callable 接口,还可以继承其他类,扩展性更好。
在大多数情况下,如果只打算覆盖 run() 方法而不打算覆盖 Thread 类其他的方法,那么应该使用 Runnable 接口。这很重要,因为除非开发者打算修改或增强类的基本行为,否则不应将类子类化。
当采用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,就无须使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。