初步认识线程
初步认识线程
概念
一个线程就是一个 “执行流”,每一个线程之间都可以按照顺序执行自己的代码,多个线程之间可以 “同步” 执行多份代码
比如说,原本一个人做的事情,现在交给三个人一起做,那么这三个人就是线程
使用原因
那么为什么要有线程呢?我们直接使用进程不可以吗?
- 需求增加,现如今更依赖于并发编程,来充分利用多核CPU
- 取决于任务场景,需要很多任务同时完成,或者避免多余的等待
而线程相较于进程而言:
- 线程是轻量级
- 创建、销毁、调度更快
区别
进程和线程是有一定区别的,同时也存在一些联系
进程是包含线程的
每一个进程都必须至少有一个线程存在,也就是主线程
进程之间不共享内存空间,同一进程内的线程之间是共用所处进程的内存空间的
进程是系统分配资源的最小单位,线程是系统调度的最小单位
我们还可以举一个简单的例子来解释:
假设我们的任务需求是组装汽车,一个人员对应一个组装台,如果此时汽车需求为100,那么这位组装人员就需要一个人组装100台汽车: 但是这样需要花费的时间就很长了
- 添加进程
当一个组装人员来完成这个任务时,我们需要很长的时间,于是此时,我们另外又聘请了一位组装人员B,B和A一样,分配了一个组装台,需要自己组装完整的车,在这种共同执行的条件下,每个人只需要组装50台车就可以完成任务
但是,这样也是有缺点的:
- 每一个人员都需要熟悉完整的组装车的流程,培训的成本也就增加了
- 我们需要为新聘请的人员增加一台新的组装台,需要的占地以及成本就增加了
- 添加线程
为了降低成本,我们还是先聘请人员B,让B和A合作,都在一台组装台进行工作,这样多的内存空间也就省下来了;同时,我们让A和B分别负责组装的一部分(比如一个负责车身,一个负责引擎),这样他们只需要培训他们负责的部分就可以了,培训成本就节省了
那么,我们可不可以为了节省成本不停的加入组装人员呢?
- 添加线程
为了降低成本,我们还是先聘请人员B,让B和A合作,都在一台组装台进行工作,这样多的内存空间也就省下来了;同时,我们让A和B分别负责组装的一部分(比如一个负责车身,一个负责引擎),这样他们只需要培训他们负责的部分就可以了,培训成本就节省了
那么,我们可不可以为了节省成本不停的加入组装人员呢?
代码演示
Java的线程和操作系统的线程是有一定区别的
操作系统: 实现了线程这样的机制,并且对用户层提供了一些API来满足用户的使用
Java: 使用标准库的 Thread类 ,可以看作对操作系统提供的API进行抽象和封装
对于多线程来说: 每一个线程都是一个独立的执行流,线程之间是 “并发” 执行的,
并发不是同时!!!
我们用一个代码来展示一下:
这里的线程就是 main(主线程)、创建的线程t
class MyThread1 extends Thread{
@Override
public void run(){
//这里的代码就是线程要完成的工作
//同时这里是一个线程
System.out.println("hello world");
while (true){
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//这里也是一个线程
Thread t = new MyThread1();
t.start();//启动新的线程
while(true){
System.out.println("main");
Thread.sleep(1000);
}
}
}
结果:
thread 和 main 是在并发式(并行/并发–>不确定)的进行
编写代码
创建线程也有很多方法
继承 Thread 类
直接使用 this 就表示当前线程对象的引用
创建一个线程类并继承 Thread,继承之后要重写 run() 方法
extends Thread
class MyThread1 extends Thread{
@Override
public void run(){
//代码块
}
}
- 创建线程实例,是一个 Thread类,因为继承,所以可以直接创建 MyThread1类,来实现创建 Thread类
Thread t = new MyThread1();
- 启动线程
t.start();//启动新的线程
实现 Runnable 接口
this 表示的是 MyRunnable 的引用,需要使用 Thread.currentThread()
实现Runnable接口
implements Runnable
class MyRunnable implements Runnable{
@Override
public void run(){
//代码块
}
}
- 创建实例,调用方法启动线程(因为不是继承,所以直接创建的 MyRunnable类 不是 Thread类)
Thread t = new Thread(new MyRunnable());
t.start();
其他创建方法
- 在匿名内部类中创建 Thread 子类对象
Thread t = new Thread(){
@Override
public void run(){
//代码块
}
};
t.start();
- 在匿名内部类中创建 Runnable 子类对象
Thread t = new Thread(new Runnable() {
@Override
public void run() {
//代码块
});
t.start();
- 使用 lambda 表达式来创建 Runnable 子类对象
Thread t = new Thread(()->{
//代码块
});
t.start();
优缺点
优点
- 提高响应性: 主线程保持响应,避免因耗时任务导致界面卡顿
- 资源利用率高: 线程共享内存和资源,减少资源消耗,适合处理大量并发任务
- 提升性能: 多核CPU上并行执行任务,充分利用硬件资源,加快处理速度
- 简化设计: 将复杂任务分解为多个线程,简化程序结构,便于维护
- 实时处理: 适合实时系统,能够同时处理多个任务,满足实时性要求
缺点
- 复杂性高: 线程同步和资源竞争问题增加了开发和调试难度
- 调试困难: 多线程程序的非确定性行为使得问题难以复现和定位
- 资源竞争: 多个线程访问共享资源时,可能导致死锁或数据不一致
- 上下文切换开销: 频繁的线程切换会消耗CPU资源,影响性能
- 内存消耗: 每个线程需要独立的栈空间,线程过多时会增加内存负担
- 线程安全问题: 需要额外措施(如锁、信号量)来保证数据一致性,增加了复杂性
常见面试题
进程 和 线程 的区别
可以从定义、资源分配、开销、独立性、并发性以及应用场景来回答
进程 | 线程 | |
定义 | 进程是操作系统分配资源的基本单位,是程序的一次执行实例 每个进程有独立的内存空间、文件描述符、系统资源 | 线程是进程内的执行单元,是CPU调度的基本单位 线程共享进程的内存空间和资源,有自己的栈和寄存器 |
资源分配 | 独立的内存和资源 | 共享进程的内存和资源 |
开销(创建、切换) | 较大 | 较小 |
独立性 | 高,进程之间互相隔离 | 低,线程之间共享资源 |
应用场景 | 需要高隔离性的任务 | 需要高并发和资源共享的任务 |