OS碎片内容
1 线程相关
1.1 基础介绍
-
程序(program):完成特定任务,用某种语言编写的一组指令的集合。就是代码,静态存在的
-
进程(progress):运行中的程序,会被系统赋给
内存空间
;进程是程序的一次执行过程,或是正在运行的程序。有它自身的产生、存在、消亡过程- 进程不仅包含正在运行的程序实体,并且包括这个运行的程序中占据的所有系统资源,比如说 CPU、内存、网络资源等
-
线程(thread):由进程创建,是进程的一个实体;一个进程实际上有多个线程
- 单线程:一个时刻只允许一个线程
- 多线程:同一时刻多个线程:QQ进程开始时,可以有多个聊天窗口;b站可以播放多个视频
- **并发:同一时刻,多个任务交替执行,貌似同时的错觉,**
单核cpu
实现的多任务就是并发 - **并行:同一时刻,多个任务同时执行,**
多核cpu
可实现并行
-
线程其实是迷你进程,比如一个军队是一个线程,有拿军粮的士兵,有狙击兵,有坦克兵,这些都是线程,都执行进程的某些功能
举个例子:同时挂三个 QQ 号,它们就对应三个 QQ 进程,退出一个就会杀死一个对应的进程。但是,就算你把这三个 QQ 全都退出了,QQ 这个程序死亡了吗?显然没有。
1.2 基本使用
如何创建
-
继承Thread,重写run方法
-
实现Runnable接口,重写run方法
-
用start启动线程
- 整个代码开始运行是开启了一个进程 -> 紧接着main线程开启 ->start可以开启新线程
- run的话,当前线程名就是main,实际上就只有main这一个线程;start的话,就是开启了新进程,当前进程就是新进程
- 当main线程启动一个子线程时,主线程不会阻塞,会继续往下执行;只有主线程的时候会阻塞
- start会允许线程同时进行,run只会让当前的执行完毕后,才轮到其他执行
-
public synchronized void start(){ start0(); } // 是本地方法,JVM调用,底层是c/c++实现 // 真正实现多线程的效果是start0() 不是run private native void start0();
- start:用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。 通过调用Thread类的start ()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run ()方法,这里方法 run ()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止
- run: run ()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
如何创建之二
- 实现Runnable接口
-
Dog dog = new Dog(); Thread thread = new Thread(dog); thread.start(); // 不能直接dog.start,需要这个方式
区别
本质上一样,但是Runnable更适合多个线程共享一个资源,同时避免了单继承的限制。
T3 t3 = new T3();
Thread tt1 = new Thread(t3);
Thread tt2 = new Thread(t3);
tt1.start();
tt2.start();
线程终止
- 线程完成任务后,会自动退出
- 可以通过
使用变量
控制run方法退出的方式停止线程,即通知方式
1.3 常用方法
- setName:设置线程名称
- getName:返回名称
- start:开始执行线程;jvm调用start0
- run:调用对象run方法
- setPriority:更改线程优先级
- getPriority:获取线程优先级
- sleep:暂停执行,一个静态方法
- interrupt:中断线程,但不是真正结束线程。一般用于中断休眠,用于唤醒
- yield:线程的礼让。让出cpu,让其他线程执行,但是礼让时间不确定,不一定成功。静态方法
- join:线程的插队,先执行插队线程的所有任务,完成之后其他的才执行
1.4 用户线程和守护线程
- 用户线程:工作线程,当线程的任务执行完成或通知方式结束
- **守护线程:**
setDaemon(true)
方法 为工作线程服务,当所有的用户线程结束的时候,守护线程自动结束。比如垃圾回收机制
1.5 线程的生命周期
- NEW:尚未启动的线程
- RUNNABLE:在jvm中执行的线程 -> 里面可以分为Ready状态和Running状态
- BLOCKED:被阻塞等待监视器锁定的线程
- WAITING:正在等待另一个线程执行****特定动作的线程
- TIMED_WAITING:正在等待另一个线程执行动作达到指定****等待时间的线程
- TERMINATED:已退出的线程
2-6是并列的
1.6 线程同步机制
- 多线程里,一些敏感数据不允许被多个线程同时访问,所以需要这个技术保证数据的完整性
- 当有一个线程在对内存进行操作时,其他线程都不会对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
-
Synchronized
-
synchronized(对象){ // 需要得到对象的锁 需要被同步的代码 } 还可以放在方法声明中,表示整个方法为同步方法 public synchronized void method(String name){ 需要被同步的代码 }
-
1.7 互斥锁
-
这是一种标记,用来保证在任一时刻,只能有一个线程访问该对象
-
关键字synchronized来与对象的互斥锁联系。当一个对象用synchronized修饰的时候,表明该对象在任一时刻只能由一个线程访问
-
同步的局限性:导致效率降低
-
同步方法(非静态)的锁可以是this,也可以是其他对象(要求是同一对象)
-
同步方法(静态)的锁为当前类.class
-
多个线程的锁对象为同一个
- 比如继承Thread类的时候,需要new多个对应类的对象,此时this指的是各对象,而不是共有的,可以在方法里共有一个object就好了
1.8 线程死锁
- 多个线程占用对方的锁资源,不肯相让,导致死锁,矛盾无法执行
1.9 释放锁
-
当前线程的同步方法、代码块已结束
- 拉屎结束
-
当前线程的同步方法、代码块遇到了break、return
- 拉屎时厕所出现了问题
-
当前线程的同步方法、代码块遇到了错误或异常
- 没带纸
-
当前线程的同步方法、代码块遇到了wait()方法,当前线程暂停,释放锁
- 便秘拉不出来
-
不会释放锁:
-
调用sleep、yield等方法
- 在厕所睡着了
-
其他线程调用了该线程的suspend方法让其挂起
-
2. 堆栈和内存空间
pen
栈区(Stack)
定义:栈是一种后进先出(LIFO, Last In First Out)的数据结构,通常用于存储局部变量和函数调用的上下文信息。
- 换一句说法就是变量的周期是在函数内部的,函数运行完变量就没了
生命周期:栈中的元素(如局部变量)在函数调用时被创建,在函数返回时被销毁。就相当于出去了就回不来了
- 比如说程序A调用B,B调用C,此时肯定C先释放内存,然后是B,然后是A
内存分配:栈的内存分配是自动的,不需要程序员手动管理。
大小:栈的大小通常较小,且有上限,这取决于操作系统和编译器。
访问速度:栈的访问速度非常快,因为它是连续的内存区域。
- 只是寄存器的移动
堆区(Heap)
定义:堆是用于动态内存分配的内存区域,程序员可以根据需要申请和释放内存。
生命周期:堆中的对象的生命周期由程序员控制,需要手动管理内存的分配和释放。
内存分配:堆的内存分配需要程序员显式请求,通常通过
malloc
,calloc
,realloc
和new
(在C++中)等函数进行。、
- 释放内存就是看情况,不会说必须先进后出,内存的分配不是连续的
大小:堆的大小通常较大,理论上可以非常大,但受限于系统的可用内存。
访问速度:堆的访问速度通常比栈慢,因为内存分配可能不是连续的,且需要额外的内存管理开销。
内存需要及时释放,否则会有内存泄漏的风险
区别
- 内存管理:栈的内存是自动管理的,而堆的内存需要程序员手动管理。
- 大小限制:栈有大小限制,而堆的大小理论上可以非常大。
- 生命周期:栈中元素的生命周期与函数调用相关,而堆中对象的生命周期由程序员控制。
- 访问速度:栈的访问速度通常比堆快。
为什么需要这两种内存区域?
- 栈:由于其快速的访问速度和自动内存管理,栈非常适合用于存储生命周期短的局部变量和函数调用信息。
- 堆:堆提供了更大的灵活性,允许程序员根据需要动态地分配和释放内存,这对于创建大型数据结构或在不确定生命周期的情况下管理内存非常有用。
check
内存可以看作一个个大小相同的隔间,一隔间存一byte(8bits),每个隔间也有一个编号,叫做内存地址。变量可以看作指针,指向的是当前数据占据的内存的第一位地址。现在大家在努力实现内存碎片的充分利用。
堆区和栈区之间其实还有数据区域,是可以扩充两区的内存地盘用的。此时OS会帮助执行扩大堆区的命令