首先要弄明白一点,Java的线程不等于操作系统级别的线程,Java创建的线程是Java虚拟机管理的,是对底层操作系统线程的封装。提供了统一的API,屏蔽了不同操作系统的差异性。
操作系统级别的线程是由操作系统内核直接管理和调度的实体。它们是 CPU 调度的基本单位。
现代 JVM 实现中,一个 Java 线程通常对应一个操作系统级别的线程,即所谓的 1:1 线程模型。常见实现方式:
- 在 HotSpot JVM(Oracle JDK / OpenJDK)中,每个 Java
Thread
对象最终都会映射到一个操作系统的原生线程(native thread)。 - 这些原生线程由操作系统进行调度,JVM 负责对其进行管理(比如启动、阻塞、唤醒等)。
C语言创建的线程,才是真正意义上的操作系统上的线程。但是C语言在类Unix使用pThread来创建线程,但是在Windows使用的是Win32 API。所以C语言不具备跨平台性,而Java创建线程,无论是什么操作系统,都是调用同一个API,只是JVM版本不同,所以Java可以跨平台。
一个运行的JVM实例,对应一个操作系统级别的进程,一个JVM进程,里面可能有多个线程。
在Java中,线程名和线程ID是Java虚拟机(JVM)内部的概念,主要用于在Java应用程序内标识和区分不同的线程。通过Thread.currentThread().getName()可以获取当前线程的名字,而通过Thread.currentThread().getId()可以获得一个唯一标识该线程的ID。
然而,这些名称和ID与操作系统层面的线程名和ID并不直接对应或相同。具体来说:
线程ID:每个Java线程都有一个在JVM范围内唯一的ID,但这个ID与操作系统的线程ID(通常可以在操作系统的进程管理工具中看到)是不同的。操作系统为每个线程分配的ID是在OS级别唯一的,且它们属于更低级别的实现细节。
线程名:你可以给Java中的线程设置名字,这对于调试和日志记录非常有用。但是,这个名字与操作系统给线程指定的名字无关。实际上,在很多情况下,操作系统并不会为单独的线程分配特定的“名字”,而是依赖于进程ID和线程ID来管理和识别线程。
因此,虽然Java提供了一种方便的方式来命名和识别线程,但这些信息主要是为了在JVM和你的Java代码之间使用,并不直接反映到操作系统的线程命名和标识机制上。如果你需要查看底层的操作系统线程信息,可能需要使用操作系统特定的工具或者API,比如Linux下的ps -T -p <PID>命令,或者Windows的任务管理器等,同时结合JVM提供的关于线程映射的信息来进行分析。
1. 进程与线程
进程:进程是程序基本运行的实体。
线程:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
启动一个JVM实例=启动一个进程
JVM同时提供多个服务=多线程
为什么要有多线程?
因为CPU处理数据的速度太快了,等待内存IO的时间或者等待磁盘的IO时间,远远大于CPU处理数据的时间,而这个等待时间,CPU完全可以去处理别的线程任务。
2. 并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。
电脑常见的参数:
- 2核4线程
- 4核8线程
- ...
- 32核64线程
表示的是一台电脑同时能够运行xxx个线程。
3. 多线程的三种实现方式
3.1 继承Thread类的方式
- 自定义一个类继承Thread
- 重写run方法
- 创建子类的对象,并调用start方法启动线程
// 1. 创建一个MyThread类继承Thread
public class MyThread extends Thread{
// 2. 重写run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
public class Main {
public static void main(String[] args) {
// 3. 创建子类对象
MyThread myThread01 = new MyThread();
MyThread myThread02 = new MyThread();
// 4. 调用子类对象的start方法开启线程
myThread01.start();
myThread02.start();
}
}
结果是myThread01和myThread02交替执行
3.2 实现Runnable接口的方式
- 自己定义一个类实现Runnable接口
- 重写里面的run方法
- 创建自己类的对象
- 创建一个Thread类的对象,将自己类传进入
public class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class Main {
public static void main(String[] args) {
// 创建任务对象
MyRun myRun = new MyRun();
// 创建线程对象, 并将myRun任务传递到t1
Thread t1 = new Thread(myRun);
// 开启线程
t1.start();
}
}
3.3 利用Callable接口和FutureTask接口方式
无论是继承Thread类创建多线程,还是实现Runnable接口实现多线程,都没有返回值,如果想获得线程的返回值,就要使用这种方式。
1. 创建一个类MyCallable实现Callable接口并重写call方法
2. 创建MyCallable对象
3. 创建FutureTask对象
4. 创建Thread类对象
// 1. 创建一个MyCallable类, 实现Callable接口
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
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 t1 = new Thread(futureTask);
t1.start();
Integer result = futureTask.get();
System.out.println(result);
}
}
4. 线程的常用方法
5. 线程池Executors
线程池 主要核心原理:
- 创建一个池子,池子是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下次再次提交任务时,不需要创建新的线程,直接复用已有的线程即可。
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
线程池代码实现:
Executors工具类:线程池的工具类,该工具类提供了很多静态方法,返回不同类型的线程池对象。属于JUC包
1. 创建线程池
// 1. 创建一个大小为5的线程池, 默认是懒加载的(任务来了才会创建线程)
ExecutorService pool = Executors.newFixedThreadPool(5);
2. 提交任务
// 2. 提交任务
pool.submit(new MyRun());
3. 所以的任务全部执行完毕,关闭线程池
// 3. 销毁线程池
pool.shutdown();
整理代码:
(1)实现Runnable接口
public class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
(2)创建线程池,提交任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
// 1. 创建一个大小为5的线程池, 默认是懒加载的(任务来了才会创建线程)
ExecutorService pool = Executors.newFixedThreadPool(5);
// 2. 提交任务
pool.submit(new MyRun());
// 3. 销毁线程池
pool.shutdown();
}
}