一、什么是多线程?
多线程是指在一个程序中同时运行多个独立的执行线程。
线程是计算机进行调度和执行任务的最小单位,一个进程可以包含多个线程,每个线程可以独立执行不同的任务。
在单线程的程序中,代码是按照顺序依次执行的。而在多线程的程序中,不同的线程可以并发地执行不同的代码片段,从而可以同时处理多个任务,提高程序的性能和并发处理能力。
多线程具有以下特点:
- 并发执行:多个线程可以同时执行,不必等待其他线程完成。
- 共享资源:多个线程可以共享同一份资源,例如内存、文件等。
- 独立性:每个线程都有自己的执行上下文和栈空间,彼此之间相互独立。
多线程可以用于实现以下功能:
- 响应用户界面:在图形界面应用程序中,使用多线程可以保持用户界面的响应性,让用户可以同时进行其他操作。
- 提高程序性能:使用多线程可以将耗时的任务分配给不同的线程并行处理,提高程序的运行效率。
- 实现并发编程:多线程可以实现并发处理,使得程序能够同时处理多个任务或请求。
进程:
是指正在运行中的程序的实例。在计算机中,每个程序在执行时都会被加载到内存中,并分配一定的系统资源,形成一个独立的进程。进程是操作系统进行资源分配和调度的基本单位。
进程具有以下特点:
- 独立性:每个进程都有自己的独立地址空间,包括代码、数据、栈等,彼此之间相互隔离,不会相互干扰或影响。
- 并发性:操作系统可以同时运行多个进程,每个进程独占一定的系统资源,通过切换和调度来实现并发执行。
- 资源管理:每个进程可以申请和使用一定的系统资源,如内存、文件、设备等,操作系统负责对这些资源进行分配和管理。
- 通信与同步:进程之间可以通过进程间通信(IPC)机制进行交互和数据共享,也可以通过同步机制实现对共享资源的访问控制。
- 生命周期:进程有创建、执行、阻塞(等待资源)、就绪(等待调度)和终止等不同的状态,通过状态转换来完成进程的生命周期。
每个进程都有一个唯一的进程标识符(PID),用于标识和管理进程。操作系统通过进程管理和调度算法来决定哪些进程可以执行、暂停或终止,并根据优先级和调度策略进行合理的资源分配。进程之间可以独立运行,也可以通过线程来实现并发执行。
总之,进程是计算机中正在运行的程序的实例,是操作系统进行资源管理和调度的基本单位。多个进程可以同时运行,彼此独立且相互隔离,通过进程间通信和同步机制实现交互和共享资源。
二、多线程两个概念
1.并发
并发是指在同一时间间隔内执行多个任务或处理多个请求的能力。在计算机领域中,这些任务可以被彼此隔离并独立执行,从而提高系统的吞吐量和性能。
与串行处理相比,并发具有以下优点:
- 提高系统吞吐量:在同一时间间隔内,可以处理更多的任务或请求,提高系统的处理能力和性能。
- 提高响应速度:通过并发处理能够保证及时响应用户请求,提高用户体验和满意度。
- 改善资源利用率:通过充分利用CPU、内存和I/O等资源,提高系统的资源利用效率和效益。
- 设计灵活性:通过并发设计可以实现各组件之间的解耦和独立性,从而提高系统的灵活性和可维护性。
在并发实现中,需要考虑以下问题:
- 竞争条件:共享资源会引起多个并发进程或线程的访问竞争,需要通过同步机制来控制对资源的访问。
- 死锁问题:并发操作中多个进程或线程之间互相等待对方完成后才能继续执行,可能导致死锁问题。
- 数据一致性:并发操作涉及到共享数据的访问和修改,需要保证数据的一致性和正确性。
- 调度与优先级:并发任务的调度和处理顺序需要根据不同的场景和优先级进行设计和调整。
2.并行
并行是指在同一时间点上同时执行多个任务或处理多个请求的能力。与并发不同,并行是在多个处理单元(如多核处理器、多线程、分布式系统等)上同时执行任务,以加快计算速度和提高系统性能。
并行具有以下优点:
- 加速计算速度:通过同时执行多个任务,可以有效地利用多个处理单元,加快计算速度,提高系统的处理能力。
- 提高系统吞吐量:并行处理能够同时处理多个请求,提高系统的吞吐量,降低请求的等待时间。
- 提高数据处理能力:通过并行运算,可以快速处理大规模数据集,提高数据处理和分析的效率。
- 增加任务的并行性:通过并行执行,可以将复杂的任务划分为多个子任务,每个子任务独立执行,从而提高任务的并行性和可扩展性。
在并行实现中,需要考虑以下问题:
- 任务划分和负载均衡:将任务合理地划分为多个子任务,并使得各个处理单元之间的负载尽量均衡,避免出现部分处理单元闲置或过载的情况。
- 数据同步和通信:在并行执行过程中,可能需要对子任务之间的数据进行同步和通信,确保数据的一致性和正确性。
- 并行算法设计:针对特定的并行任务,需要设计相应的并行算法,以充分利用并行计算资源,提高计算效率和性能。
- 硬件结构和拓扑:在分布式系统或多核处理器中,考虑硬件结构和拓扑的因素,合理规划任务的调度和数据的传输方式。
三、多线程的实现方式
1.继承Thread类:
- 创建一个继承自Thread类的子类,并重写run()方法,在run()方法中编写线程需要执行的任务逻辑。
- 通过创建子类的实例,调用start()方法来启动线程,start()方法会自动调用run()方法。
class MyThread extends Thread {
public void run() {
// 线程执行的任务逻辑
for (int i = 0; i < 5; i++) {
System.out.println("Thread: " + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
// 主线程继续执行其他任务
for (int i = 0; i < 5; i++) {
System.out.println("Main: " + i);
}
}
}
2.实现Runnable接口:
- 创建一个实现了Runnable接口的类,并实现其中的run()方法,在run()方法中编写线程需要执行的任务逻辑。
- 创建Runnable接口的实例,并将其作为参数传递给Thread类的构造函数。
- 调用Thread类的start()方法来启动线程。
class MyRunnable implements Runnable {
public void run() {
// 线程执行的任务逻辑
for (int i = 0; i < 5; i++) {
System.out.println("Thread: " + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
// 主线程继续执行其他任务
for (int i = 0; i < 5; i++) {
System.out.println("Main: " + i);
}
}
}
3.利用Callable接口和Future接口方式实现
使用Callable接口和Future接口可以实现多线程任务的执行和结果获取。Callable接口代表一个具有返回值的任务,通过call()方法定义任务逻辑并返回结果。Future接口表示异步计算的结果,可以用来获取线程执行的结果、取消任务等操作。
下面是利用Callable接口和Future接口实现的详细步骤:
定义一个类,实现Callable接口,并指定泛型类型为线程任务返回值的类型。在该类中重写call()方法,编写线程需要执行的任务逻辑,并返回结果。
import java.util.concurrent.Callable;
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
// 线程任务逻辑
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
return sum;
}
}
创建Callable对象,并使用FutureTask类将Callable对象包装成一个Future对象。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) {
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
}
}
创建一个线程,并将FutureTask对象作为参数传递给Thread的构造函数。
Thread thread = new Thread(futureTask);
启动线程并等待任务执行完毕。
thread.start();
使用Future接口的方法获取线程执行的结果。
- get():阻塞当前线程,直到任务执行完毕并返回结果。可以在get()方法中指定超时时间。
- isDone():判断任务是否执行完毕。
- cancel():取消任务的执行。
try {
// 获取线程执行结果
int result = futureTask.get();
System.out.println("线程执行结果:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
完整示例代码如下:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
// 线程任务逻辑
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
return sum;
}
}
public class Main {
public static void main(String[] args) {
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
try {
// 获取线程执行结果
int result = futureTask.get();
System.out.println("线程执行结果:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
使用Callable接口和Future接口可以更灵活地控制线程任务的执行和结果获取。通过将Callable对象包装成FutureTask对象,并通过Thread启动线程,最后使用Future接口的方法获取线程结果,实现了多线程任务的执行和结果获取的功能。