线程,作为程序执行的最小单元,能够让程序在同一时间内处理多个任务,避免因单任务阻塞而造成的资源浪费。无论是处理大量用户请求的服务器端应用,还是需要同时进行数据计算与 UI 交互的客户端程序,多线程都扮演着至关重要的角色。
一、线程的创建
在 Java 编程中,多线程是实现并发执行的重要手段,它能让程序同时处理多个任务,提升运行效率。以下将详细介绍 Java 中创建多线程的三种常用方式,包括它们的实现步骤、优缺点及适用场景。
1、通过继承Thread的方式实现
Thread 类是 Java 中用于线程操作的基础类,通过继承该类可以直接创建线程。
实现步骤:
- 自定义一个类,继承 Thread 类。
- 重写 Thread 类中的 run () 方法,在该方法中编写线程要执行的任务代码。
- 创建自定义类的实例对象。
- 调用实例对象的 start () 方法启动线程(注意:不可直接调用 run () 方法,否则会当作普通方法执行,无法实现多线程并发)。
package org.example;
public class MyThread extends Thread{
// 构造方法,用于设置线程名称
public MyThread() {}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
// 线程执行的任务:输出10条信息
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100); // 休眠100毫秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() + "hello" + i);
}
}
}
// 启动线程
public class Main {
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程1");
MyThread thread2 = new MyThread("线程2");
thread1.start();
thread2.start();
}
}
优点:
- 编程简单直接,可直接使用 Thread 类中的方法(如 getName () 获取线程名称)。
缺点:
- 扩展性较差,由于 Java 是单继承机制,继承了 Thread 类后就不能再继承其他类。
适用场景:简单的多线程任务,且不需要继承其他类的场景。
2、 通过Runnable 接口实现
Runnable 接口是 Java 中定义线程任务的接口,通过实现该接口可以更灵活地创建线程。
实现步骤:
- 自定义一个类,实现 Runnable 接口。
- 重写 Runnable 接口中的 run () 方法,编写线程任务代码。
- 创建自定义类的实例对象(该对象表示线程要执行的任务)。
- 创建 Thread 类的实例对象,将自定义类的实例作为参数传入 Thread 的构造方法中。
- 调用 Thread 实例的 start () 方法启动线程。
package org.example;
public class MyRunnable implements Runnable{
@Override
public void run() {
// 线程执行的任务:输出100条信息
for (int i = 0; i < 100; i++) {
Thread t = Thread.currentThread(); // 获取当前线程对象
System.out.println(t.getName() + "hello" + i);
}
}
}
// 启动线程
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable, "线程1");
Thread thread2 = new Thread(myRunnable, "线程2");
thread1.start();
thread2.start();
}
}
优点:
- 扩展性强,实现 Runnable 接口的同时还可以继承其他类。
- 适合多个线程共享同一个任务资源的场景。
缺点:
- 编程相对复杂,不能直接使用 Thread 类中的方法,需通过 Thread.currentThread () 获取当前线程对象后再调用相关方法。
适用场景:多线程共享资源的场景,或需要继承其他类的场景。
3、通过 Callable 接口结合 FutureTask 类实现
Callable 接口与 Runnable 接口类似,但它可以获取线程执行的返回结果,适用于需要获取线程执行结果的场景。
实现步骤:
- 自定义一个类,实现 Callable 接口(需指定返回值类型)。
- 重写 Callable 接口中的 call () 方法,编写线程任务代码,并返回执行结果。
- 创建自定义类的实例对象(表示线程任务)。
- 创建 FutureTask 类的实例对象,将 Callable 实例作为参数传入(用于管理线程执行结果)。
- 创建 Thread 类的实例对象,将 FutureTask 实例作为参数传入。
- 调用 Thread 实例的 start () 方法启动线程。
- 调用 FutureTask 实例的 get () 方法获取线程执行的返回结果
package org.example;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程执行的任务:计算1到100的和
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
// 启动线程并获取结果
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
Integer result = futureTask.get(); // 获取线程执行结果
System.out.println(result);
}
}
优点:
- 可以获取线程执行的返回结果,功能更强大。
- 扩展性强,实现 Callable 接口的同时可以继承其他类。
缺点:
- 编程最为复杂,需要结合 FutureTask 类来管理返回结果。
适用场景:需要获取线程执行结果的场景,如多线程计算并汇总结果的任务。
二、线程的常见方法
线程的方法是控制线程行为的关键,掌握这些方法能让我们更好地管理线程的执行顺序、状态等。以下将详细介绍 Java 线程中常见的方法及其使用场景。
线程的常见方法如下:
- 1、getName() 返回该线程的名称
- 2、setName(String name) 设置线程的名字
- 3、static Thread currentThread() 获取当前线程对象
- 4、static void sleep(long time) 让线程休眠的时间,单位是毫秒
- 5、setPriority(int newPriority) 设置线程的优先级
- 6、final int getPriority()获取线程的优先级
- 7、final void setDaemon(boolean on)
- 8、static void yield()出让线程/礼让线程
- 9、final void join()插入线程/插队线程
1、线程名称相关方法
- String getName():返回当前线程的名称。若未手动设置,线程会有默认名称(格式为 “Thread-x”,x 为数字)。
- void setName(String name):设置线程的名称,也可通过线程的构造方法设置名称。
// 通过构造方法设置名称
MyThread thread1 = new MyThread("飞机");
// 通过setName()方法设置名称
MyThread thread2 = new MyThread();
thread2.setName("坦克");
2、获取当前线程对象
- static Thread currentThread():获取当前正在执行的线程对象。在 main 方法中调用时,获取的是 main 线程对象。
// 在Runnable实现类的run()方法中获取当前线程
public class MyRunnable implements Runnable{
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
}
3、线程休眠
- static void sleep(long time):让当前线程休眠指定的时间(单位为毫秒),休眠期间线程会释放 CPU 资源,时间结束后重新参与 CPU 调度。
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100); // 休眠100毫秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() + "hello" + i);
}
}
4、线程优先级
- setPriority(int newPriority):设置线程的优先级,优先级范围为 1-10,默认优先级为 5,优先级越高,线程获取 CPU 资源的概率越大(但不保证一定先执行)。
- final int getPriority():获取线程的优先级。
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable, "飞机");
Thread thread2 = new Thread(myRunnable, "坦克");
thread1.setPriority(10); // 设置最高优先级
thread2.setPriority(1); // 设置最低优先级
thread1.start();
thread2.start();
5、守护线程
- final void setDaemon(boolean on):设置线程为守护线程(当参数为 true 时)。守护线程是为其他线程服务的,当所有非守护线程执行完毕后,守护线程会自动结束。
MyThread thread1 = new MyThread("飞机"); // 非守护线程
MyThread2 thread2 = new MyThread2("坦克"); // 守护线程
thread2.setDaemon(true);
thread1.start();
thread2.start(); // 当thread1执行完毕后,thread2会陆续结束
6、线程礼让
- public static void yield():出让当前线程的 CPU 执行权,让其他线程有机会执行,但当前线程仍可能再次获取 CPU 资源。
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "hello" + i);
Thread.yield(); // 出让CPU执行权
}
}
7、线程插队
- public final void join():让调用该方法的线程插入到当前线程之前执行,当前线程会等待插入的线程执行完毕后再继续执行。
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread thread1 = new MyThread("飞机");
thread1.start();
thread1.join(); // 让thread1插入到main线程之前执行
// main线程会等待thread1执行完毕后再执行以下循环
for (int i = 0; i < 10; i++) {
System.out.println("main" + i);
}
}
}
通过上述方法的灵活使用,我们可以有效地控制线程的执行流程,实现多线程之间的协作与同步,满足不同场景下的并发需求。