多线程(multithreading)是指在一个程序中同时执行多个不同的线程(thread),每个线程都是程序的一部分,是独立的执行路径。相比于单线程程序,多线程程序可以更充分地利用计算机的多核心或多处理器资源,提高程序的运行效率和用户体验。在多线程编程中需要注意线程同步、锁、死锁等问题。
常见的多线程实现方式有以下几种:
1、继承Thread类方式
这种方式是定义一个类继承Thread类,然后重写run()方法,将要执行的任务写在run()方法中。创建线程实例后,调用start()方法启动线程。该方式实现简单,但如果类已经继承了其他类,则无法使用。
以下是一个继承Thread类的多线程案例:
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
这个案例中,我们创建了一个继承了Thread类的MyThread类,并在它的run方法中打印了当前线程的名字和一个循环计数器。然后在主函数中,我们创建了两个MyThread对象并启动它们。当程序运行时,两个线程会交替打印出它们的计数器值,因为它们是同时运行的。
这个案例展示了如何继承Thread类来创建一个线程,并且启动线程的方法是调用start方法。每个线程都有自己的执行上下文,包含自己的栈空间和局部变量,因此两个线程之间的计数器值是相互独立的。
2、实现Runnable接口方式
Java实现Runnable接口方式的代码如下:
- 定义一个类实现Runnable接口,重写run()方法
public class MyRunnable implements Runnable {
@Override
public void run() {
// 编写线程执行的任务代码
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
- 创建MyRunnable实例并将其作为参数传入Thread类的构造方法中
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
- 启动线程并执行任务
当调用Thread的start()方法时,JVM会自动调用MyRunnable实例的run()方法,从而执行线程的任务。
注意:在这种方式下,多个线程可以共享同一个MyRunnable实例,这意味着多个线程可以同时执行同一个任务。如果需要多个线程执行不同的任务,需要为每个线程创建一个单独的Runnable实例,并将其传入Thread的构造方法中。
这种方式是定义一个类实现Runnable接口,重写run()方法,将要执行的任务写在run()方法中。调用Thread类的构造函数,将该实现Runnable接口的对象作为参数传入,创建Thread实例后调用start()方法启动线程。该方式相比于继承Thread类方式,更加灵活,因为一个类可以实现多个接口。
3、实现Callable接口方式
Callable接口是Java提供的一个线程执行结果的返回值接口,需要通过Future接口获取执行结果。下面是Java实现Callable接口的示例代码:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) throws Exception {
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
Integer result = futureTask.get();
System.out.println("计算结果:" + result);
}
}
在上面的代码中,我们通过实现Callable接口,实现了call()方法,返回了一个Integer类型的结果。我们使用FutureTask类将Callable对象封装成一个异步任务,然后启动一个线程执行这个异步任务。最后,通过FutureTask的get()方法获取异步任务的执行结果。
注意:FutureTask的get()方法会阻塞当前线程,直到异步任务执行完毕并返回结果。如果异步任务执行时间过长,会导致当前线程一直阻塞。为了避免这种情况,我们可以使用FutureTask的get(long timeout, TimeUnit unit)方法,设置一个超时时间,超过这个时间后如果异步任务还没有执行完毕,则抛出TimeoutException异常,当前线程继续执行。
该方式类似于实现Runnable接口方式,但是需要实现Callable接口的call()方法,并返回一个结果。可以通过FutureTask类将Callable转换成Runnable来启动线程并获取返回值。
4、实现线程池方式
Java中线程池可以通过以下步骤实现:
1. 创建一个ExecutorService对象,它是Java中线程池的基本实现类之一,也是一个抽象类,可以使用Executors类中的静态方法来创建。
ExecutorService executorService = Executors.newFixedThreadPool(5);
这里创建了一个固定大小为5的线程池。
2. 在需要执行任务的地方,使用submit方法提交一个Runnable对象或Callable对象。
executorService.submit(new Runnable() {
@Override
public void run() {
// 执行任务代码
}
});
3. 执行完任务后,记得关闭线程池。
executorService.shutdown();
线程池的好处在于它可以避免重复创建和销毁线程,从而提高了程序的性能和效率。同时,线程池还可以控制并发线程的数量,防止系统崩溃。
通过使用线程池,可以更好地管理和复用线程,避免了线程的频繁创建和销毁。使用线程池是一种高效的多线程实现方式。
每种方式都有各自的特点,需要根据具体的实际情况选择合适的方式。