操作系统概念

参考:https://www.cnblogs.com/zhuzhu2016/p/5804875.html
进程和线程的区别
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。是指一段正在执行的程序。一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程
线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。是CPU调度和分派的基本单位。
一个程序至少一个进程,一个进程至少一个线程。
一个程序可以包括多个进程(视频+发消息…)
一个进程可以包括多个程序(双开…)需要共享DDL文件

一个进程应该包括:
1.程序的代码。
2.程序处理的数据。
3.程序计数器中的值,指示下一条将运行的指令;
4.一组通用的寄存器的当前值,堆,栈;
5.一组系统资源(如打开的文件,网络。IO等)

1 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
2 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
3 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
4 线程是处理器调度的基本单位,但是进程不是。
5 两者均可并发执行。

进程上下文

原文:https://blog.csdn.net/gatieme/article/details/51872659

当一个进程从内核中移出,另一个进程成为活动的, 这些进程之间便发生了上下文切换. 操作系统必须记录重启进程和启动新进程使之活动所需要的所有信息. 这些信息被称作上下文, 它描述了进程的现有状态, 进程上下文是可执行程序代码是进程的重要组成部分, 实际上是进程执行活动全过程的静态描述, 可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等

上下文切换(有时也称做进程切换或任务切换)是指CPU从一个进程或线程切换到另一个进程或线程

进程的上下文信息包括, 指向可执行文件的指针, 栈, 内存(数据段和堆), 进程状态, 优先级, 程序I/O的状态, 授予权限, 调度信息, 审计信息, 有关资源的信息(文件描述符和读/写指针), 关事件和信号的信息, 寄存器组(栈指针, 指令计数器)等等

处理器总处于以下三种状态之一
1. 内核态,运行于进程上下文,内核代表进程运行于内核空间;
2. 内核态,运行于中断上下文,内核代表硬件运行于内核空间;
3. 用户态,运行于用户空间。

步骤:
挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处,
在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复
跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程

为什么会有线程?
1 每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通(系统开销大响应用户请求效率低),因此操作系统中线程概念被引进。
2 线程的执行过程是线性的,尽管中间会发生中断或者暂停,但是进程所拥有的资源只为改线状执行过程服务,一旦发生线程切换,这些资源需要被保护起来。
3 进程分为单线程进程和多线程进程,单线程进程宏观来看也是线性执行过程,微观上只有单一的执行过程。多线程进程宏观是线性的,微观上多个执行操作。
4 线程的改变只代表CPU的执行过程的改变,而没有发生进程所拥有的资源的变化。

优缺点:
线程执行开销小,但是不利于资源的管理和保护。线程适合在SMP机器(双CPU系统)上运行。
进程执行开销大,但是能够很好的进行资源管理和保护。进程可以跨机器前移。

何时使用多进程,何时使用多线程?
对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。

进程的五种状态:原文:https://blog.csdn.net/cafucwxy/article/details/78453430
在这里插入图片描述
1 创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态

2 就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行
就绪——>运行:运行的进程的时间片用完,调度就转到就绪队列中选择合适的进程分配CPU

3 执行状态:进程处于就绪状态被调度后,进程进入执行状态
运行——>就绪:1,主要是进程占用CPU的时间过长,而系统分配给该进程占用CPU的时间是有限的;2,在采用抢先式优先级调度算法的系统中,当有更高优先级的进程要运行时,该进程就被迫让出CPU,该进程便由执行状态转变为就绪状态。
运行——>阻塞:正在执行的进程因发生某等待事件而无法执行,则进程由执行状态变为阻塞状态,如发生了I/O请求

4 阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用。
阻塞——>就绪:进程所等待的事件已经发生,就进入就绪队列

5 终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行

以下两种状态是不可能发生的:
阻塞——>运行:即使给阻塞进程分配CPU,也无法执行,操作系统在进行调度时不会从阻塞队列进行挑选,而是从就绪队列中选取
就绪——>阻塞:就绪态根本就没有执行,谈不上进入阻塞态。

线程的几种状态
  在Java当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
  第一是创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
  第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
  第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
  第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
  第五是死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
从运行态到阻塞态的原因有:1.I/O释放 2.同步块释放 3.锁阻塞 4.主动睡眠 5.注定wait
从阻塞态到就绪态的原因有:1.I/O释放 2.同步块释放 3.获得锁 4.睡眠时间截止 5.notify已执行

在这里插入图片描述

线程同步的几种方法: 原文:https://blog.csdn.net/tectrol/article/details/80705787
1.同步方法和静态方法。
即由synchronized关键字修饰的方法。
这里得注意:1.静态的方法,当不同的实例对象进入,是不受影响的,因为普通方法是属于对象的,多线程下用同一对象访问才会出现锁。2.静态方法是属于类的,所以同一线程下,不同的实例方法在进入时都会被锁住。
注:synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

  public synchronized static void save()
  }
 public synchronized void save(){
 }

2.同步代码块
即由synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

 synchronized(object){ 
    }

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

3.使用特殊域变量(volatile)实现线程共享变量(不一定可以同步)(适用于一写多读的场景)
volatile关键字为域变量的访问提供了一种免锁机制, 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新, 因此每次使用该域就要重新计算,而不是使用寄存器中的值 。volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 。所以不能保证线程安全。
注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。
用final域,有锁保护的域和volatile域可以避免非同步的问题。

4.使用重入锁实现线程同步
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

Lock lock = new ReentrantLock();
lock. lock();
try {
    System. out. println("获得锁");
} catch (Exception e) {
    // TODO: handle exception
} finally {
    System. out. println("释放锁");
    lock. unlock();
}

5.使用局部变量实现线程同步 (单线程可见)
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal 类的常用方法
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

synchronized和Lock的区别:
1.用法不一样:synchronized可以加在特定的方法上,也可以加在特定代码块中,而Lock需要显式地指定起始位置和终止位置。例:开始位置:lock.lock();结束:lock.unlock();
2.性能不一样:ReentrantLock不仅拥有和synchronized相同的并发性和内存语义,还多了锁投票,定时锁,等候,中断锁等,性能十分稳定,在竞争不激烈下,synchronized比Lock的性能要好一点,反之,synchronized性能会下降得很快。
3.锁机制不一致:synchronized获得锁和释放锁的方式都是在块结构中,当获取多个锁时,必须以相反的顺序释放,并且时自动解锁的,不会因为出现异常而导致没有被释放从未引发死锁。而Lock需要开发人员手动去释放,并且必须在finally块中释放,否则会引起死锁问题。
4.实现机制不一样,synchronized依赖于JVM,而ReentrantLock依赖于JDK。

synchronized的底层实现:
对象的实例包括三部分:对象头,实例数据,对齐填充。
对象头:对象哈希码,对象分代年龄,指向锁记录的指针,指向重量级锁的指针,空(GC标记),偏向线程ID,偏向时间戳,对象分代年龄。
同步代码块中:
synchronized的对象锁,其指针指向的是一个monitor对象(由C++实现)的起始地址。每个对象实例都会有一个 monitor。其中monitor可以与对象一起创建、销毁;亦或者当线程试图获取对象锁时自动生成。
当多个线程同时访问一段同步代码时,会先存放到 _EntryList 集合中,接下来当线程获取到对象的monitor时,就会把_owner变量设置为当前线程。同时count变量+1。如果线程调用wait() 方法,就会释放当前持有的monitor,那么_owner变量就会被置为null,同时_count减1,并且该线程进入 WaitSet集合中,等待下一次被唤醒。
若当前线程顺利执行完方法,也将释放monitor,重走一遍刚才的内容,也就是_owner变量就会被置为null,同时_count减1,并且该线程进入 WaitSet集合中,等待下一次被唤醒。
如果当前线程获取锁失败,那么就会被阻塞住,进入_WaitSet 中,等待锁被释放为止。
修饰在方法上:
字节码中并没有monitorenter指令和monitorexit指令,取得代之的是ACC_SYNCHRONIZED标识,JVM通过ACC_SYNCHRONIZED标识,就可以知道这是一个需要同步的方法,进而执行上述同步的过程,也就是_count加1,这些过程。

多线程中 synchronized 锁升级的原理是什么?
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

AQS
AQS其实就是一个可以给我们实现锁的框架
内部实现的关键是:先进先出的队列,state状态
定义了内部类ConditionObject
拥有两种线程模式独占模式和共享模式。
在Lock包中的相关锁(ReentrantLock,ReadWriteLock)都是基于AQS来构建,一般我们叫AQS为同步器。

Java中的线程池:ThreadPoolExecutor类
参数介绍:
corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择

ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略,有以下四种取值:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

创建线程池有哪几种方式?
线程池创建有七种方式,最核心的是最后一种:

newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;

newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;

newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;

newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;

newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;

newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;

ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。

线程池都有哪些状态?
RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。

线程池中 submit() 和 execute() 方法有什么区别?
execute():只能执行 Runnable 类型的任务。
submit():可以执行 Runnable 和 Callable 类型的任务。

在 Java 程序中怎么保证多线程的运行安全?
方法一:使用安全类,比如 Java. util. concurrent 下的类。
方法二:使用自动锁 synchronized。
方法三:使用手动锁 Lock。

守护线程与用户线程
守护线程又被称为“服务进程”。是指在程序运行时在后台提供一种通用服务的线程。这种线程并不属于程序中不可或缺的部分,任何一个守护线程都是整个JVM中所有非守护线程的“保姆”。通过将线程设置:t.setDaemon(true),可将线程设置为守护线程。
当所有的非守护线程结束,守护线程没工作可做,也会立刻停止。
当一个守护线程中产生了其他线程,那么这些新产生的线程默认还是守护线程。

线程的常用方法:
public Thread(Runnable target,String name) 创建线程时,可以设置线程名字。
public final synchronized void setName(String name) 设置线程名称的普通方法:
public final String getName( )取得线程名称的普通方法:
public static native void sleep(long millis) throws InterruptedException; 线程暂缓执行,等到预计时间之后再恢复执行。
public static native void yield(); 暂停当前正在执行的线程对象,并执行其他线程。
public final void join() throws InterruptedException { join(0); }指的是如果在主线程中调用该方法时就会让主线程休眠,让调用join()方法的线程先执行完毕后再开始执行主线程。//可以设置参数(毫秒数),代表主线程会等待多少毫秒之后继续执行。
线程停止:1.设置标记位,让线程正常停止。2.使用stop()方法强制使线程退出,但是使用该方法不安全,已经被废弃了!
3. 使用Thread类的interrupt()方法中断线程。
isAlive() 判断线程是否处于死亡态或新建态。

并发与并行:
并发:在某个时间段内,多任务交替处理的能力
1.并发程序之间有相互制约的关系,直接制约体现为一个程序需要另一个程序的计算结果;间接制约体现为多个程序竞争共享资源。
2.并发程序的执行过程是断断续续的。程序需要记忆现场指令及其执行点
3.当并发数设置合理并且CPU拥有足够的处理能力时,并发会提高程序的运行效率。
并行:同时处理多任务的能力

CPU与内存

CPU内部结构图
在这里插入图片描述
CPU是一个高内聚的模块化组件,它对外部其他硬件设备的时序协调,指令控制,存取操作,都需要通过操作系统进行统一管理和协调。
高级语言提供的多线程技术和并发更多地依赖于操作系统地调配,并行更多依赖于CPU多核技术。

CAS:Compare and Swap的缩写,翻译过来就是比较并替换。
CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
synchronized属于悲观锁,悲观的认为程序中的并发情况严重,所以严防死守,CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。
CAS的缺点:
1) CPU开销过大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。
2) 不能保证代码块的原子性
CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
3) ABA问题
这是CAS机制最大的问题所在。

1 java语言CAS底层如何实现?
利用unsafe提供的原子性操作方法。
2.什么是ABA问题?怎么解决?
当一个值从A变成B,又更新回A,普通CAS机制会误判通过检测。
利用版本号比较可以有效解决ABA问题。

银行家算法:
当一个进程申请使用资源的时候,银行家算法通过先 试探 分配给该进程资源,然后通过安全性算法判断分配后的系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待。
在这里插入图片描述

同步阻塞,同步非阻塞,异步阻塞,异步非阻塞
摘自:https://blog.csdn.net/qq_38128179/article/details/86132731
在这里插入图片描述

缓存模型

在这里插入图片描述

L1 Cache 容量小,需保证速度快。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值