一.认识cpu:
通过一些基础门电路组成的,cpu为什么跑得快? -> cpu集成程度比较高
![](https://i-blog.csdnimg.cn/direct/c3ebfb6a955a4c8c81dee0104cee365b.png)
2.50GHz叫做cpu的主频,数字越大算的越快,表示1s执行25亿条指令
二.操作系统
1.简介操作系统
操作系统是一个“软件”->是电脑上最重要,最复杂的软件之一
操作系统是一个“管理的软件”:
1.对下,管理好各种硬件设备
2.对上,给各种软件提供一个稳定的运行环境
重点介绍,操作系统对“进程”这个软件资源的管理。
2. 进程
(1)什么是进程?
进程是资源分配的最小单位,进程就是跑起来的程序,steam.exe,qq.exe都是可执行文件,静静的躺在硬盘上,不会对系统有任何影响,一单双击exe,操作系统就会把exe文件加载到内存上,并让cpu执行exe内部的一些指令,这个时候就把exe执行起来,开始执行一些具体的操作。
这些运行起来的可执行的文件,成为“进程”
Java代码最终都是通过java进程跑起来的,(此处的java进程就是常说的JVM)
(2)线程是什么?
线程是进程内部的一个部分,进程包含线程
(3)操作系统如何管理进程
1.先描述一个进程(明确出一个进程上面的一些相关属性)PCB(进程控制块)
2.再组织诺干个进程(使用数据结构,将这些PCB组织到一起,方便进行增删改查)
典型的案例就是使用双向链表将PCB组织起来(Linux)
所谓的创建进程。就是先创建出PCB,然后将PCB加入到双向链表
(4)PCB
想要一个进程正常工作,就需要给这个进程分配一些系统资源:内存,硬盘,CPU
PCB的基础属性:
1.PID :进程的身份标识
2.内存指针:指明了这个进程要执行的指令在内存的哪里,以及这个进程执行中以来的数据在哪里。 补充:每当运行一个exe,操作系统就会把这个exe加载到内存中,变成进程,此时进程的指令,数据都在内存中
3.文件描述符表:程序运行过程中,经常要和文件打交道(文件是在硬盘上的)进程每次打开一个文件,就会在文件描述符表上多增加一项(这个文件描述符表可以视为一个数组,里面的每个原色又是一个结构体,对应一个文件的相关信息)。补充:一个进程只要一启动,不管是否对文件进行操作,都会打开三个文件(标准输入System.in,标准输出System.out,标准错误System.err)
上面的属性是基础属性,下面的属性主要是为了实现进程调度:
状态:就绪、运行、挂起、停止等状态。
优先级:有静态优先级(进程创建后优先级不会改变)和动态优先级
上下文:表示了上次进程被调出CPU的时候,程序的执行环境,下次进程上CPU时,可以恢复到之前的环境,然后继续执行
记账信息:统计每个进程,都分别被执行了多久
(5)进程调度
所谓的调度,就是时间管理
**先来先服务(FCFS)调度算法**:它既可以用于作业的调度,又可以用于进程调度。FCFS属于不可剥夺(抢占)算法。从表面上看,它对所有作业都是公平的
** 短作业优先(SJF)调度算法:**该算法对长作业不利,SJF中长作业的周转时间会增加。更糟的是,若一旦有长作业进入系统的后备队列,由于调度程序总是优先调度那些短作业(即使是后来的短作业也会被优先安排给处理机),导致长作业长期不被调度,饿死在后备队列中。
** 优先级调度算法:**1.非剥夺(抢占)式优先级调度算法:当一个进程正在处理机上运行时,即使有某个更在重要或者紧迫的进程进入就绪队列,仍然让正在运行的进程继续运行,直到由于自身的原因而主动让出处理机时(任务完成或等待),才把处理机分配给更重要或紧迫的进程。
2.剥夺式优先级调度算法:当一个进程正在处理机上运行,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给更重要或紧迫的进程。
而根据进程创建后其优先级是否可以改变,可以将进程优先级分为静态优先级和动态优先级。
** 高响应比优先调度算法:**响应比Rp = (等待时间+要求服务时间)/要求服务时间
** 时间片轮转调度算法:**在时间片轮转的调度算法中,时间片的大小对系统性能有很大影响。
**多级反馈队列调度算法:**
(6)进程内存
操作系统如何分配进程的内存?
为了保证进程的独立性->“虚拟地址空间”->进程通过虚拟地址空间,已经各自隔离开了
但是实际工作中,进程之间还是需要相互交互的,操作系统会提供“公共空间”->进程间通信
操作系统中提供了各种各样的“公共空间”,操作系统中提供了多种这样的进程间通信机制,现在最主要使用的进程间通信方式:文件操作、网络操作(Socket)
三.多线程
1.线程是什么?
(1)线程的出现
为什么要有进程-> 因为我们支持多任务,程序员也需要“并发编程”
通过多进程完全可以实现并发编程,但是有问题->1.如果需要频繁的创建、销毁资源,成本较高->创建进程既要分配资源,销毁进程也要释放资源
2.频繁进行调度进程,成本较高
如何解决进程的这个问题?
1.进程池->进程池消耗的系统资源太多了
2.使用线程实现并发编程->因为线程比进程给更轻量,每个线程也能执行一个任务,创建、销毁、调度线程的成本比进程更低,正因如此在Linux上也把线程成为轻量级的进程(LWP ->light weight process)
(2)线程—轻量级进程
进程重在哪? ->进程资源申请和释放
线程是包含在进程中的,**一个进程**中有**多个线程**共用同一份资源
只是创建第一个线程的时候(需要资源分配)成本是较高的,在后续这个进程中其他线程的创建成本都会更低一些,因为不需要在分配资源了
经典面试题:进程和线程的区别和联系:
1.进程包含线程,一个进程可以有一个线程,也可以有多个线程
2.进程和线程都是为了处理并发编程这样的场景
但是进程有问题,需要频繁分配释放资源,成本太高,相比之下线程更加轻量
3.操作系统创建进程,给进程分配资源,进程是资源分配的最小单位
操作系统创建的线程,是要在CPU上调度执行,线程是操作系统调度的最小单位
4.进程具有独立性,每个进程有各自的虚拟存储空间,一个进程挂了,不会耽误其他进程
一个进程包含若干线程,共用一片内存,一个线程挂了可能会影响其他进程,甚至导致整个进程崩溃
2.多线程编程
**Thread类和Runnable接口:**
Java标准库中提供了一个Thread类,来表示/操作线程。创建好的Thread实例,其实和操作系统中的线程是一一对应的关系,Thread类实现了Runnable接口,Runnable接口是描述了一个任务。
(1)最基本创建线程的方法:
class MyThread extends Thread{
@Override
public void run() {
System.out.println("test");
}
}
//最基本的创建线程的方法
public class demo1 {
public static void main(String[] args) {
Thread t=new MyThread();
t.start();
}
}
写一个子类MyThread继承Thread类,重写run方法(run方法是描述了这个线程内部要执行那些代码,每个线程是并发执行的,因此就需要告诉线程执行的代码是什么)。并不是定义好这个类,写好方法就创建线程了,需要调用start方法才真正在系统中创建了线程,才真正执行run方法。
线程之间是并发执行的,上述例子看不出并发执行的效果
(2)main线程和t线程并发执行
class MyThread1 extends Thread{
@Override
public void run() {
while(true) {
System.out.println("test");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class demo2 {
public static void main(String[] args) {
Thread t=new MyThread1();
t.start();
while(true){
System.out.println("main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
sleep函数:Thread.sleep(1000)->加限制让线程休眠一秒再打印
中断异常:InterruptedException ->线程被强行中断的异常,受查异常
在一个进程中至少会有一个进程,在一个java进程中,至少会有一个main方法的线程 ,自己创建的t线程和自动创建的main线程,就是并发执行的关系。代码运行结果:
当t线程和main线程休眠时间到了,先唤醒哪个线程是不确定的,操作系统来说,内部对现成的调度顺序,在宏观上认为是随机的(抢占式执行)
(3)创建一个类,实现Runable接口,再创建Runable实例传给thread实例
class MyRunable implements Runnable{
@Override
public void run() {
System.out.println("test");
}
}
public class demo3 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunable());
t.start();
}
}
通过Runable描述任务的内容,进一步再把描述好的任务传给Thread实例
再使用Thread的构造方法,进行线程的创建
(4)就是上面写法的翻版,使用匿名内部类
public class demo4 {
public static void main(String[] args) {
//匿名内部类
Thread t=new Thread(){
@Override
public void run() {
System.out.println("test");
}
};
t.start();
}
}
创建了一个匿名内部类,继承自Thread类,同时重写run方法,同时在new出这个匿名内部类的实例
public class demo5 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable(){
@Override
public void run() {
System.out.println("test");
}
});
t.start();
}
}
创建一个实现Runnable接口的匿名内部类,重写run方法再new出实例传给Thread。
创建线程总结:我们通过认为Runnable这种写法更好一点,能够做到让线程和线程执行的任务,更好的解耦,Runnable只是单纯的描述了一个任务,至于这个任务让谁执行和Runnable没关系,但是使用Thread是直接创建了线程,并安排了线程中的任务。
(5)使用lambda代替了Runnable
public class demo6 {
public static void main(String[] args) {
Thread t=new Thread(()->{
System.out.println("test");
});
t.start();
}
}
3.多线程的效率
测试:将两个数字a和b自增到1000000000,并行执行的时候,时间大概是600多ms,两个线程并发执行时间大概是400ms,提升了接近50%。
4.线程的其他属性和方法
(1)Thread(String name)和jconsole
这个构造方法是给线程取一个名字,可以使用jconsole来观察现成的名字
jsconsole路径: ![](https://i-blog.csdnimg.cn/direct/f83cde96f5504266ad8b74b94ed1fd4b.png)
hzh是我们创建的线程:
显示这个代码执行到哪里:
(2)是否是后台线程isDaemon
如果线程是后台线程,就不影响进程退出,如果线程是前台线程,就会影响进程退出
创建的t线程是前台线程,就算main执行完毕,进程也不能退出
(3)操作系统中对应的线程是否正在运行isAlive
Thread t对象的生命周期和内核中对应的线程,生命周期并不一致
创建t对象之后,调用start之前系统是没有线程的,在run执行完之后,线程就被销毁了,但是这个t对象可能还存在,通过isAlive就能判定线程的运行情况
在调用start之后,run结束之前isAlive就是true
调用start之前,run结束之后isAlive就是false
(4)Thread一些重要的方法
one.start:决定了系统是否真正创建线程
two.中断线程:让线程停下来
->关键是让线程对应的run方法执行完(特殊的main线程,就是让main方法执行完,线程就完了)
1)手动设置一个标志位,来控制这个线程是否要执行结束(可以在主线程中更改标志位的值来控制线程的执行)
2)使用Thread中内置的一个标志位来进行判定
可以通过
Thread.interrupted()->静态方法
Thread.currentThread().isInterrupted()这个方法判定的标志位是每个实例的标志位
public class demo7 {
public static void main(String[] args) {
Thread t=new Thread(()->{
while(!Thread.currentThread().isInterrupted()) {
System.out.println("test");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
在main方法里先对main线程休眠5s,再t.interrupt();就是对t现成的标志位进行设置,这样就能影响到t线程中的标志位,进而控制线程结束。
three.线程等待,join
线程之间调度顺序是不确定的,是按照调度器进行安排的,整个过程可以视为随机,但是我们想要控制线程之间执行的顺序,就可以使用到线程等待。
join方法:哪个线程调用join,哪个线程就会阻塞等待,等到对应线程的run方法执行完毕
public class demo8 {
public static void main(String[] args) {
Thread t=new Thread(()->{
while(!Thread.currentThread().isInterrupted()) {
System.out.println("test");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
t.start();
try {
t.join(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
此时是main线程调用这个方法,针对t这个对象调用的,此时就是让mian线程等待t线程执行结束->t的run方法执行结束,调用join方法后,mian线程就进入阻塞状态(阻塞状态就是无法上CPU执行)
通过线程等待,就是在控制让t先结束,main后结束,一定程度上控制了这两个线程的执行顺序
join操作,在默认情况下就是死等->不合理->提供了另一个版本,就是可以执行等待时间
join(10000)就是十秒内t完成就join直接返回,十秒之后main线程直接不等了
four.获取当前线程的引用
Thread.currentThread();->哪个线程调用的curretThread()就返回哪个线程的实例
five.线程的休眠 sleep
如果是一个进程有多个线程,此时每个线程都有一个PCB,一个进程就对应一组PCB了,PCB上有一个字段tgroupId,这个id相当于进程的id,同一个进程中的诺干个线程的tgroupId是相同的。
如果某个线程调用了sleep方法,就会把线程从就绪队列拿到阻塞队列上。
版权归原作者 HZH030126 所有, 如有侵权,请联系我们删除。