操作系统中最核心的概念就是进程,进程是对正在运行中的程序的一个抽象,是系统进行资源分配和调度的基本单位。
操作系统的其他所有内容都是围绕着进程展开的,负责执行这些任务的是CPU。
进程一般由程序、数据集合和进程控制块三部分组成:程序用于描述进程要完成的功能,是控制进程执行的指令集。数据集合是程序在执行时所需要的数据和工作区。程序控制块,包含进程的描述信息和控制信息,是进程存在的唯一标志。
线程(thread)是操作系统能够进行运算调度的最小单位,其是进程中的一个执行任务(控制单元),负责当前进程中程序的执行。
一个进程至少有一个线程,一个进程可以运行多个线程,这些线程共享同一块内存,线程之间可以共享对象、资源,如果有冲突或需要协同,还可以随时沟通以解决冲突或保持同步。
在Java的Thread线程类中,有一个枚举类,枚举了线程的各个状态:
public enum State {
// 线程还没有启动
NEW,
// 运行状态
RUNNABLE,
// 线程阻塞,无法获得想要获得的资源,可能是资源正在被锁定,或等待被解锁
BLOCKED,
// 由于调用其中一个线程,线程处于等待状态
WAITING,
// 具有指定等待时间的等待线程的线程状态
TIMED_WAITING,
// 终止线程的线程状态
TERMINATED;
}
创建一个类继承Runnable接口,测试thread每个周期的状态:
package myTest;
public class myTest implements Runnable {
public static void main(String[] args) throws InterruptedException {
myTest test = new myTest();
Thread thread = new Thread(test);
System.out.println(thread.getState());
thread.start();
System.out.println(thread.getState());
thread.sleep(1000);
System.out.println(thread.getState());
}
public String sout() {
return "test";
}
@Override
public void run() {
System.out.println(this.sout());
// System.out.println("runnning");
}
}
/**
NEW
RUNNABLE
test
TERMINATED
*/
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。
在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。
所以任务从保存到再加载的过程就是一次线程切换。
1. 阻塞
一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。阻塞可以分很多情况,一种是临时阻塞,就是当时要访问的资源暂时被锁住的了无法访问,所以就停留在此地,另一种是碰到了死锁,就会一直被阻塞,直到设计者发现它。
2 无饥饿(Starvation-Free)
如果线程之间是有优先级的,那么线程调度的时候总是会倾向于满足高优先级的线程。也就是说,对于同一个资源的分配,是不公平的。锁也分公平锁和非公平锁,对于非公平锁来说,系统允许高优先级的线程插队。这样就有可能导致低优先级的线程产生饥饿。但是如果是公平锁,满足先来后到,那么饥饿就不会产生,不管新来的线程优先级多高,要想获得资源,就必须乖乖排队。这样所有的线程都有机会执行。
3 无障碍(Obstruction-Free)
无障碍是一种最弱的非阻塞调度。无障碍表示两个线程可以畅通无阻的行动,但也会出现一些矛盾,比如一起访问同一个资源,又一起修改一个资源,那样就会出现数据的不可见性,对于无障碍的线程来说,一旦检测到这种情况,它就会立即对自己所做的修改进行回滚,确保数据安全。
4 无锁(Lock-Free)
无锁的并行都是无障碍的。但是这样会出现很多很多问题,比如说也是同时访问和修改资源,如果发生了资源的争夺,可能会陷入长时间的阻塞或者是直接奔溃,这对于程序来说,无疑是致命的。
5 无等待(Wait-Free)
无锁只要求一个线程可以在有限步数内完成操作,而无等待则是在无锁的基础上更进一步进行扩展,它要求所有的线程都必须在有限步数内完成,这样就不会引起线程饥饿问题。