1. 线程(Thread)
1.1 概念
1.1.1 线程是什么
一个线程就是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行着多份代码。
1.1.2 为什么要有线程
首先,“并发编程”成为“刚需”
- 单核 cpu 的发展遇到了瓶颈,要想提高算力,就需要多核 cpu,而并发编程能更充分利用多核 cpu 资源
- 有些任务场景需要“等待 IO”,为了让等待 IO 的时间能够去做一些其他工作,也需要用到并发编程
其次,虽然多进程也能实现并发编程,但是线程比进程更轻量
- 创建线程比创建进程更快(因为创建线程省去了“分配资源”的过程,一旦创建进程,同时也会创建第一个线程,此时就会分配资源,一旦后续创建更多线程,就不必重新再分配资源了)
- 销毁线程比销毁进程更快(因为销毁线程省去了“释放资源”的过程)
- 调度线程比调度进程更快
最后,人们不满足于线程的轻量,又有了“线程池(ThreadPool)”和 “协程(Coroutine)”
1.1.3 进程和线程的区别
- 进程包含线程,每个进程至少有一个线程存在,即主线程
- 进程是系统“资源分配”的基本单位,线程是系统“调度执行”的基本单位
- 进程和进程之间不共享内存空间,同一个进程的线程之间共享同一份系统资源(包括内存、硬盘、网络宽带等;在编程中,多个线程是可以公用同一份变量的)
- 线程是当下实现并发编程的主流方式,通过多线程就可以充分利用好多核 cpu,但并不是线程数目越多越好,线程数目达到一定程度,把多个核心充分利用后,此时若继续增加线程,无法再提高效率,甚至可能会影响效率
- 多个线程之间,可能会相互影响(线程安全问题),一个线程抛出异常,可能会把其他线程一起带走
- 多个进程之间,一般不会相互影响,一个进程崩溃了,不会影响到其他进程,这一点称为“进程的隔离性”
1.1.4 Java 的线程和操作系统线程的关系
线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些 API 供用户使用(如 Linux 中的 pthread 库)
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装
1.2 创建线程
方法1:继承 Thread 类
继承 Thread 来创建一个线程类
//此处 Thread 类可以直接使用,不需要导入任何包,因为其包含于 java.lang
class MyThread extends Thread {
//继承 Thread 的主要目的就是为了重写 run 方法
@Override
public void run() {
//这里写的代码,就是即将创建出的线程要执行的逻辑
System.out.println("hello Thread");
}
}
public class Demo1 {
public static void main(String[] args) {
//创建 MyThread 类的实例
MyThread t = new MyThread();
//调用 start 方法
//会在进程内部创建出一个新的线程
//新的线程就会执行 run 里面的代码
t.start();
}
}
执行结果:
tip:上面 run 方法,用户手动定义了,但没有手动调用,最终这个方法被 系统/库/框架 进行调用了,此时这样的方法就称为“回调函数”(callback)
例如:
- C 中的函数指针主要有两个用途:1. 作为回调函数;2. 实现转移表,降低代码的复杂程度
- Java 数据结构中的优先级队列(堆),必须先定义好对象的“比较规则”;如:Comparable 中的 compareTo 和 Comparator 中的 conpare,都是自己定义,但没有调用,此时都是由标准库本身内部的逻辑负责调用的
上面代码运行起来是一个进程,但是这个进程中包含了两个线程
调用 main 方法的线程称为“主线程”;t.start(); 又手动创建了新的线程
主线程和新线程就会 并发/并行 的在 cpu 上执行
由于程序只执行一次,速度很快,看不出线程相关,所以给程序加上 while 循环:
class MyThread extends Thread {
//继承 Thread 的主要目的就是为了重写 run 方法
@Override
public void run() {
//这里写的代码,就是即将创建出的线程要执行的逻辑
while(true) {
System.out.println("hello Thread");
//循环中加上休眠操作,让循环每循环一次都休息一会,避免 cpu 消耗过大
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//创建 MyThread 类的实例
MyThread t = new MyThread();
//调用 start 方法创建线程
t.start();
while(true) {
System.out.println("hello main!");
Thread.sleep(1000);
}
}
}
我们查看运行结果可发现:多个线程之间,谁先去 cpu 上调度执行,这个过程是“不确定的”(此处不是数学意义的随机)
这个调度顺序,取决于操作系统内核里“调度器”的实现
调度器里有一套规则,但是我们作为应用程序开发,没法进行干预,也感受不到,只能把这个过程近似的视为“随机”(抢占式执行),并不属于伪随