目录
1. 什么是线程呢?
线程是CPU能够进行运算调度的最小单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务,互相之间并不同步。
线程是独立调度和分派的基本单位。线程不拥有系统资源,同一进程中的多条线程将共享该进程中的全部系统资源,但每个线程拥有自己运行必须的数据结构,如虚拟机栈、本地方法栈、程序计数器。由于线程共享进程资源,所以,系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
那么,进程是什么呢?
2. 什么是进程呢?
进程是程序的一次执行过程,是系统运行程序的基本单位,因此,进程是动态的。系统运行一个程序也就是一个进程从创建、运行到消亡的过程。当我们启动 main
函数时其实就是启动了一个 JVM 的进程,而 main
函数所在的线程就是这个进程中的一个线程,也称主线程。
线程和进程最大的不同在于:
- 各个进程是独立的,而线程不一定。
- 线程执行开销小,但不利于资源的管理和保护。而进程相反。
3. 什么是多线程呢?
多线程,是指从软件或者硬件上实现多个线程并发执行的技术。多线程可以提升系统的整体处理性能。多线程可能会加快程序的运行速度,此外,在一些等待的任务实现上,如用户输入、文件读写和网络收发数据等时,可以释放一些资源(如CPU)。
那并发和并行,一样吗?不一样。
- 并发:在同一时间间隔内,多个任务都在执行。也就是,在这一段时间内,多个任务交替执行。
- 并行:在同一时刻,多个任务同时执行。
那么,为什么要使用多线程呢?
4. 为什么要使用多线程呢?
- 从计算机底层来说,线程间的切换和调度,其成本远小于进程。多核CPU也就是说多个线程可以同时运行,从而减少线程上下文切换的开销。
- 从互联网发展来说,系统的并发量很大,而多线程并发编程是开发高并发系统的基础。多线程机制可以大大提高系统的性能和并发能力。
总的来说,多线程可以加快查询运行速度,提剩系统性能和并发能力。
注 —上下文切换:
当前任务使用完CPU分配的时间片时,在切换到另外一个任务之前会先保存自己的状态,以便下次切换到该任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
5. 怎么创建线程?
线程创建有三种方式:
- 继承
Thread
类; - 实现
Runnable
接口; - 实现
Callable
接口。
1)继承 Thread 类
创建线程的三步:
实现过程:
- 继承
Thread
接口 - 重写
run()
方法 - 创建目标对象
- 调用
start()
代码实现
public class MyThread extends Thread {
// 重写run()方法
public void run() {
for (int i=0; i<50; i++) {
System.out.println("这是子线程"+i);
}
}
public static void main(String[] args) {
MyThread t = new MyThread();
// 主线程调用start()方法
t.start();
for (int i=0; i<100; i++) {
System.out.println("主线程"+i);
}
}
}
2)实现 Runnable 接口
创建线程的三步:
实现过程:
- 实现
Runnable
接口 - 重写
run()
方法 - 创建目标对象
- 创建线程对象,通过该线程对象来开启线程。
- 调用
start()
代码实现
public class MyThread implements Runnable {
// 重写run()方法
public void run() {
for (int i=0; i<50; i++) {
System.out.println("这是子线程"+i);
}
}
public static void main(String[] args) {
MyThread thread = new MyThread();
Thread t = new Thread(thread);
// 主线程调用start()方法
t.start();
for (int i=0; i<100; i++) {
System.out.println("主线程"+i);
}
}
}
3)实现 Callable 接口
实现过程:
- 实现
Callable
接口,需要返回值类型 - 重写
call()
方法,需要抛出异常 - 创建目标对象
- 创建执行服务。
ExecutorService service = Executors.newFixedThreadPool(1)
- 提交执行。
Future<Boolean> results = ser.submit(t)
- 获取结果。
boolean res = result.get()
- 关闭服务。
service.shutdownNow()
代码实现
// 实现Callable接口
public class MyThread implements Callable {
// 重写call()方法
public void call() {
for (int i=0; i<50; i++) {
System.out.println("这是子线程"+i);
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
// 创建执行服务
ExecutorSerice service = new ExecutorService(nThread:1);
// 提交执行
Future<Boolean> result1 = service.submit(t1);
// 获取结果
boolean r1 = result1.get();
// 关闭服务
service.shutdownNow();
}
}
4)run() 和 start() 的区别
执行 run()
的线程:线程执行完之后,主线程才继续执行。
执行 start()
的线程:线程和主线程同时执行。
6. 线程的6个状态有哪些?
线程一般有5个状态:创建、就绪、运行、阻塞、结束。
1)java 中的6个状态
在Java中,线程具有以下6种状态。线程会随着代码的执行在不同的状态间切换。
- 初始状态。线程被创建,此时还没有调用
start()
方法。 - 运行状态。包括就绪和运行两种状态。线程创建之后处于初始状态,调用
start()
方法之后,处于可运行状态,在该状态下,线程获得CPU的时间片后就处于运行状态。 - 等待状态。线程执行
wait()
方法之后,线程进入等待状态。在该状态下,线程需要其他线程的通知才能够回到运行状态。 - 超时等待状态。超时等待状态相当于在等待状态的基础上加上了超时限制,当超时时间到了后,线程会进入运行状态。通过
sleep(millis)
或wait(miliis)
可以让线程进入超时等待状态。 - 阻塞状态。当线程调用同步方法时,如果没有获得锁,线程会进入阻塞状态。
- 终止状态。当前线程已执行完毕。线程运行完
run()
方法之后,会自然终止,进入终止状态。
2)比较 sleep()
和 wait()
两者都可以暂停线程的执行。
sleep() | wait() | |
---|---|---|
是否释放锁(主要的区别) | 没有释放 | 释放 |
用途 | 暂停执行 | 线程交互/通信 |
是否会自动苏醒 | 会 | 不会,需要其他线程的通知(调用 notify() ) |
参考链接:
https://www.bilibili.com/video/BV1V4411p7EF?from=search&seid=11937559633016699466
https://snailclimb.gitee.io/javaguide/#/docs/java/multi-thread/2020%E6%9C%80%E6%96%B0Java%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93?id=_11-%e4%bd%95%e4%b8%ba%e8%bf%9b%e7%a8%8b