- 什么是线程?线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际执行单元。线程和进程类似,但线程是进程中的一个独立运行的部分,是进程中的轻量级进程。
- 多线程是什么?多线程(Multi-threading)是指在一个进程中同时运行多个线程,每个线程执行其自己的任务。多线程可以让程序在同一时刻执行多个任务,从而提高程序的执行效率和系统的资源利用率。
多线程用在哪儿?有什么好处?
- 用户界面(UI)响应性:在图形用户界面应用程序中,多线程可以用来确保UI的流畅响应。例如,一个线程负责处理用户输入和更新界面,而另一个线程在后台执行耗时的任务,如文件下载或数据处理,这样用户就不会因为长时间等待而感到应用程序无响应。
- 服务器并发处理:在服务器端编程中,多线程可以用来处理多个客户端的并发请求。每个客户端的请求可以由一个独立的线程来处理,这样可以提高服务器的吞吐量和响应速度。
- 并行计算:在需要大量计算的场景中,如科学计算、图像处理、加密解密等,多线程可以用来将任务分解成多个子任务,每个子任务由一个线程并行执行,从而加快计算速度。
- 异步I/O操作:在进行文件读写、网络通信等I/O密集型操作时,多线程可以用来实现异步操作,即在等待I/O操作完成的同时,其他线程可以继续执行其他任务,从而提高程序的效率。
- 多核处理器利用:随着多核处理器的普及,多线程编程可以更好地利用多核处理器的计算能力,通过将任务分配到不同的核心上并行执行,提高整体的处理速度。
- 游戏开发:在游戏开发中,多线程可以用来处理游戏逻辑、图形渲染、物理模拟、网络通信等多个方面,以提供更流畅的游戏体验。
- 实时系统:在实时系统中,多线程可以用来确保关键任务的及时执行,同时处理其他非关键任务,以满足系统的实时性要求。
- 数据处理和分析:在大数据处理和分析中,多线程可以用来并行处理数据集,加快数据处理的速度,提高分析效率。
多线程编程虽然能够提高程序的性能和响应性,但也需要开发者注意线程同步、资源竞争、死锁等问题,以确保程序的稳定性和正确性。
- 如何在程序中创建线程?
有四种方法创建新的执行线程:
1)将类声明为Thread的子类(继承),该子类应该覆盖(重写)类Thread的run方法,然后可以分配和启动子类的实例【继承Thread + 重写un方法】
优点:代码简单
缺点:只是继承Thread类,无法继承其他类,不利于功能的扩展。
2)声明一个实现接口Runnable接口的类,该类然后实现run方法。然后可以分配和启动子类的实例。【实现Runnable + 重写run方法 + 把runnable任务交给Thread实例】
优点:任务类只是实现接口,仍然可以继承其他类、实现其他接口,扩展性强。
缺点:多创建一个任务类对象(其实无所谓)
3)声明一个实现Callable接口的类。
4)使用线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 创建一个 Runnable 任务
Runnable myRunnable = () -> {
System.out.println("Task is running in thread: " + Thread.currentThread().getName());
};
// 提交任务给线程池
executorService.submit(myRunnable);
// 创建一个 Callable 任务并获取 Future 对象
Callable<String> callableTask = () -> {
// 模拟一些工作
Thread.sleep(1000);
return "Callable Task Result";
};
Future<String> future = executorService.submit(callableTask);
try {
// 获取 Callable 任务的结果
String result = future.get(); // 这会阻塞,直到任务完成
System.out.println("Result from Callable: " + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
executorService.shutdown();
}
}
}
- 多线程注意事项:
1)启动线程必须是调用start方法,不是调用run方法(否则,整个程序本质上就是一个实例调用其方法,直到程序完成,只有一条主线程在推进)
2)子线程一定要在主线程之前启动。(否则,整个程序相当于是单线程的一个效果)
- 对比四种线程创建方法的不同点:上面的总结
这三种方法的主要区别如下:
- 继承Thread类:这种方法简单易用,但有以下缺点:
- 不能多继承,因为Java不支持多继承,如果需要继承其他类,则不能继承Thread类。
- 线程类的子类对象可以直接当作线程使用,但是这种做法可能会导致线程与具体的任务过于耦合,不利于代码的重用和扩展。
- 实现Runnable接口:这种方法可以避免继承Thread类的缺点,可以在多个线程中重用同一个Runnable对象,更加灵活和可扩展。
- Callable和Future:这种方法可以获取线程执行结果,并且支持捕获线程执行过程中的异常。但是需要注意,FutureTask对象只能使用一次,如果需要重复获取线程执行结果,则需要创建多个FutureTask对象。
总的来说,选择哪种方法取决于具体的应用场景和需求。如果仅仅需要执行一些简单的任务,可以使用继承Thread类的方法;如果需要更灵活和可扩展的线程对象,可以使用实现Runnable接口的方法;如果需要获取线程执行结果,可以使用Callable和Future方法。
解惑???为什么FutureTask 和runnable可以作为线程的目标,但是callable不行?
FutureTask
和 Runnable
可以直接作为线程的目标,但是 Callable
不行,原因在于它们的设计和接口定义。
1. Runnable 接口: Runnable
是一个接口,它有一个 run()
方法,这个方法是线程执行的入口点。当你创建一个实现了 Runnable
接口的类的实例,并将该实例传递给 Thread
对象时,Thread
的 start()
方法会调用 Runnable
实例的 run()
方法。
class MyRunnable implements Runnable {
public void run() {
// 执行的代码
}
}
2. FutureTask 类: FutureTask
是一个实现了 RunnableFuture
接口的类,它同时实现了 Runnable
接口和 Future
接口。因此,FutureTask
可以作为 Thread
的目标,并且提供了获取异步计算结果的能力。
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
3. Callable 接口: Callable
是一个接口,它有一个 call()
方法,这个方法是线程执行的入口点,但它与 Runnable
的 run()
方法不同,call()
可以返回一个值,并且可以抛出异常(包括检查型异常)。然而,Callable
本身并没有实现 Runnable
接口,因此不能直接传递给 Thread
对象。
要让 Callable
作为线程的目标,你需要将它封装在一个 FutureTask
或者其他实现了 Runnable
接口的包装器中。FutureTask
提供了一个接受 Callable
对象的构造函数,这样 Callable
的 call()
方法就可以被 FutureTask
的 run()
方法调用。
Callable<Integer> callable = () -> {
// 执行的代码,并返回结果
return 123;
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask); thread.start();
总结来说,在Java中,线程接收的对象需要实现 Runnable
接口或者继承 Thread
类。Runnable
和 FutureTask
可以直接作为线程的目标,因为它们实现了 Runnable
接口,而 Callable
需要通过 FutureTask
或类似的包装器间接地作为线程的目标,因为 Callable
没有实现 Runnable
接口,但它提供了异步计算结果的获取能力。这种设计允许 Callable
任务既可以执行,也可以返回结果,而 Runnable
任务只能执行。