1.操作系统
操作系统是一组做计算机资源管理的软件的统称。目前常见的操作系统有:Windows系列,Unix系列,Linux系列,OSX系列,Android系列,IOS系类等。
1.1操作系统的定位
操作系统有两个基本的功能:
防止硬件资源被空的应用滥用;
向应用程序提供简单一致的机制来控制复杂而又通常大相径庭的低级硬件设备。
1.2什么是进程/任务
每个应用程序运行在现代操作系统之上时,系统会提供一种抽象,好像系统上只有这个程序在运行,所有的硬件资源都被这个程序在使用。这种假象是通过抽象了一个进程的概念来完成的,进程可以说是计算机科学中最重要和最成功的概念之一。
进程是操作系统对一个正在运行的程序的一种抽象,换而言之,可以把进程看作程序的一次运行过程;同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位。
2.进程管理
我们拥有这么多的进程,我们是先让谁执行,谁等待,这时就需要进程管理系统。
所谓管理,其实就是分为两步:
描述一个进程:使用结构体/类,把一个进程有哪些信息,表示出来。
组织这些进程:使用一定的数据结构,将这些数据结构/对象,放到一起按照一定的顺序执行。
2.1结成结构体的属性
进程的结构体(进程管理块,简称PCB,process control block)具体有哪些属性呢?
pid:每个进程都有唯一的一个身份标识。
内存指针:当前这个进程使用的内存是哪一部分(进程要跑起来,需要消耗一定的硬件资源,比如内存硬盘,CPU等)
文件描述符表:硬盘上储存的数据,往往是以文件为单位进行整理的。每次打开一个文件,就会产生一个“文件描述符”(标识了这个被打开的文件)。一个进程可能会打开很多文件,对应了一组文件描述符。把这些文件描述符放到一个顺序表这样的结构里,就构成了文件描述符表。
其他属性,都是描述和CPU资源相关的属性。这些属性都是辅助进行资源调度的
2.2进程的状态
简单的认为进程的状态有两种:就绪态和阻塞态。
就绪态:该进程已经准备好相应的资源,随时可以在CPU上执行。
阻塞态:一些资源并没有准备好,导致程序暂时无法在CPU上执行。
运行态:进程占用处理器,进行运行。
2.3进程的优先级
进程的定都不一定是公平的,是有优先调度的。
进程优先级高的先处理,进程优先级低的后处理。
2.4进程的上下文
上下文:就是描述当前进程执行到哪里这样的“存档记录”。
进程在离开CPU的时候就要把当前运行的中间结果存档,“存档”等到下次进程回到CPU上,在恢复到原来的存档,从上次的结果继续往后进行
2.5进程的并行与并发
并行:同一时刻,两个核心同时执行,此时这两个进程就是并行执行的。
并发:一个核心,先执行进程1,执行一段时间后,再去执行进程2,在执行一段时间,再去执行进程3,这样来回切换。只要切换的速度足够快,看起来就像进程1,2,3就是同时执行的。
【通俗的例子】
并行:学校餐厅有10个窗口,每次就能同时给10个学生打饭。
并发:每个餐口每个小时给学生打饭总数。
通常情况下,我们把并行和并发统称为并发
【课外知识】在一些老式的山寨机上,只允许使用一个进程。比如:打开了QQ,其他程序不会进入后台运行,而是直接退出,在打开上一个进程(程序)的时候,我们需要重新开始进程。
2.6内存管理(Memory Manage)
操作系统对内存资源的分配,采用的是空间模式——不同的进程使用内存中的不同区域,相互之间不会干扰。
2.6.1虚拟地址
系统实际上给每个进程分配的都是虚拟地址。
【解析】每个进程都对应一段虚拟地址,通过页表的进行查询真实地址。
【优势】通过虚拟地址。我们可以增加进程的隔离性。,大大提高了操作系统的稳定性。如果进程1出现错误,如野指针,通过虚拟地址,不能找到真实的物理地址,就不会对进程2产生影响。
2.7线程通信(Inter Process Communication)
有时候,需要进程之间的相互配合。
所谓进程间通信,就是在隔离性的前提下,找一个公共区域,让两个进程借助这个区域来完成数据交换。
操作系统提供的进程间通信具体实现方式,有很多种,管道,信息队列,共享内存等。对于Java学习,这里只听听就好。
3. 线程
3.1线程的概念
线程(thread)是操作系统能够进行运算调度的最小单位。进程中包含至少一条线程。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并发执行不同的任务。
3.1.1生动的例子
进程就像工厂,包含流水线(线程),场地和原材料(资源)等。
当我们增加线程的时候,就相当于增加了流水线,从而提高了效率。
每个流水线(线程)可以进行同一任务(进程)的不同部分,这样可以提高效率。但是我们发现他们公用同样的资源,如果一个线程出现的问题,将会导致其他的线程也挂掉。
3.2进程和线程的区别(重点)
进程包含线程;
进程有自己独立的内存空间和文件描述符。同一进程中的多个线程之间,共享同一份地址空间和文件描述表;
进程是操作系统资源分配的基本单位,线程是操作系统调度的基本单位;
进程之间具有独立性,一个进程挂了,不会影响到其他的进程;同一个进程里的多个线程之间,一个线程挂了,可能导致整个程序都挂了,影响到其他的线程。
3.3创建线程(重点)
我们可以使用Thread类来创建新的线程。
我们可以在
中来查看运行的线成(我们要用管理员的身份运行,否则有些时候无法查看线程)
3.3.1继承Thread类
class MyThread extends Thread {
@Override
public void run() {
while(true) {
System.out.println("hello MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDome1 {
public static void main(String[] args) {
Thread t = new MyThread();
//不会创建新的线程,而是在main线程中运行。
//t.run()
//会创建新的线程
t.start();
while(true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
继承Thread类来创建线程。并重写方法run()来执行另一个线程的任务。
当我们在主方法调用的时候,我们要t.start()来创建新的线程,如果使用t.run()并不会创建新的线程,而是在main线程上执行任务。
此时我们打开jconsole来查看线程(程序必须执行)
3.3.2实现接口Runnable
class MyRunnable implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("hello MyRunnable");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDome2 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
while(true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
我们让一个类实现Runnable接口,在把这个类传递给new Thread(new Runnable)方法,我们就可以开始这个线程,效果和上一个一样。
3.3.3使用匿名内部类
我们发现上述方法中,我们是想创建一个新的线程,但是为了这个线程我们不得不实现一个类(继承Thread或实现接口Runnable),这个类我们基本不会重复使用,此时我们就可以使用匿名内部类来实现创建一个新的线程。
继承Thread的匿名内部类
public class ThreadDome3 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
}
这种方法,大大减少了代码的长度,使代码变得简洁。
我们使用jconsole来观察线程(必须在运行)
main线程消失的原因是:main线程运行完毕,没有进行死循环。我们无法捕捉到。
实现接口Runnable的匿名内部类
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
}
3.3.4使用Lambda表示式(重点)
首先我们要知道在什么条件下才能使用lambda表达式:
首先是接口;
其次是只使用一个接口;
接口中只有一个抽象方法。
【注意】如果是函数式接口源码中有@FunctionalInterface标注
这些条件决定了只有上述的方法能实现lambda表达式创建线程。
public class ThreadDome4 {
public static void main(String[] args) {
Thread t = new Thread( () -> {
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
3.4为线程创建名字
线程是非常抽象的,我们可以为每一个线程起一个独一无二的名字,这样我们就能分清楚不同的线程。
public static void main(String[] args) {
Thread t = new Thread( () -> {
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "我的线程");
t.start();
}
3.5Thread中几种常见的属性
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否是后台线程 | isDaemon |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
ID是线程的唯一标识,不同线程不会重复;
名称是各种调试工具用到的;
状态表示线程当前所处的一个情况(默认为前台线程);
优先级高的线程理论上来说更容易被调度到;
关于后台线程,需要记住一点:JVM会在一个进程的所有非后台结束后,才会结束
运行。(可能会导致后台线程没有运行完毕,进程就结束了)
是否存活,可以简单理解为,run()方法是否已经启动且尚未终止(线程是调度的基本单位,当执行到thread.isAlive()时,可能为true,表示线程已经启动且尚未终止,如果Thread.sleep(1000),返回的结果可能是false)(多是情况下都是false)