1 什么是进程?
通俗地解释为:计算机中正在执行的一个程序实例。进程它是系统分配资源的基本单位。
想象一下,你的电脑就像是一个大工厂,而每一个进程就像是这个工厂里的一条生产线或者一个工作小组,它们各自独立地运行着不同的任务,但同时又受到整个工厂(即操作系统)的管理和调度。
2 什么是线程?
线程(Thread)是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程可以拥有多个线程,这些线程共享该进程的地址空间和系统资源,但各自拥有独立的执行栈和程序计数器,以便能够独立地执行指令序列。
我们原来写的代码都是单线程。
3 为什么实现多线程?
3.1 进程与线程的关系
可以想象一个工厂代表一个进程,而工厂内的工人则代表进程中的线程。
+------------------------+
| 工厂 | <-- 进程
| (操作系统分配的资源) |
| +-----+-----+-----+ |
| |工人1|工人2|工人3| | <-- 线程
| +-----+-----+-----+ |
| ... |
| +-----+-----+-----+ |
| |工人N|空闲位置|空闲位置| <-- 更多线程或可扩展性
| +-----+-----+-----+ |
+------------------------+
在这个模型中,每个工人(线程)都在工厂(进程)内独立工作,但他们共享工厂的资源(如工具、原材料等),这些资源相当于进程中的内存、文件句柄等。
3.2 多线程的优势
提高CPU利用率:
+------------------------+
| 工厂 |
| (CPU核心) |
| +-----+-----+-----+ |
| | CPU1| CPU2| CPU3| | <-- 多核CPU
| +-----+-----+-----+ |
| |工人1|工人2|工人3| | <-- 每个CPU核心运行一个线程
| +-----+-----+-----+ |
+------------------------+
在多核CPU上,每个核心可以独立运行一个线程,从而提高了CPU的利用率。当某个线程等待I/O操作时,CPU可以切换到另一个线程继续工作
4 java中如何创建多线程
java中提供了三种实现多线程的方式:
第一种: 继承Thread类
第二种: 实现Runnable接口
第三种: 实现Callable接口
4.1继承Thread
类
这是创建线程的最基本方式之一。通过继承Thread
类并重写其run()
方法,你可以定义线程执行的任务。然后,创建该类的实例并调用其start()
方法来启动线程。
// 继承Thread类
class MyThread extends Thread {
@Override
public void run() {
// 在这里编写线程要执行的任务
System.out.println("线程运行中:" + Thread.currentThread().getName());
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 启动线程
}
}
4.2 实现Runnable
接口
实现Runnable
接口是另一种创建线程的方式。这种方式相比继承Thread
类更加灵活,因为Java不支持多重继承,但可以实现多个接口
。通过实现Runnable
接口,你可以将线程的任务与线程的实现分离。然后,创建Thread
类的实例,将实现了Runnable
接口的类的实例作为构造器参数传递。
// 实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
// 在这里编写线程要执行的任务
System.out.println("线程运行中:" + Thread.currentThread().getName());
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start(); // 启动线程
}
}
4.3实现Callable
接口
Callable
接口是Java 5中引入的,与Runnable
接口类似,但它可以返回一个结果,并且可以抛出异常。要实现Callable
接口,你需要创建一个实现了该接口的类,并实现其call()
方法。然而,你不能直接通过Callable
接口来启动线程,因为Thread
类并不接受Callable
作为构造器参数。相反,你需要使用FutureTask
类来包装Callable
对象,FutureTask
同时实现了Future
和Runnable
接口。然后,你可以像使用Runnable
一样,通过Thread
来执行FutureTask
。
// 实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 在这里编写线程要执行的任务
return "线程运行结果";
}
}
public class CallableExample {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread t1 = new Thread(futureTask);
t1.start(); // 启动线程
try {
// 获取Callable任务执行的结果
System.out.println("线程执行结果:" + futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
Callable
接口比Runnable
接口提供了更强大的功能,特别是当你需要线程返回执行结果时。然而,这也意味着使用Callable
会比使用Runnable
稍微复杂一些,因为你需要处理Future
对象以及可能抛出的异常。