操作系统笔记

1、死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。

死锁产生的四个必要条件?

  • 1、互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 2、请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 3、不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 4、环路等待条件:在发生死锁时,必然存在一个进程—资源的环形链。

解决死锁的基本方法?

预防死锁:

  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件
  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件

避免死锁:

  • 银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。

    当进程请求资源时,假设同意该请求,看系统的状态,安全,同意,不安全,阻塞

     Resource(系统每种资源的总量)
    
     Available (未分配的每种资源总量)
    
     Claim        (该请求对资源的请求)
    
     Allocation(当前分配给进程的资源)
    

检测死锁:

1、每个进程和资源都有唯一号码

2、建立资源分配表和进程等待表

解除死锁:

1、剥夺资源:从其他进程种,抽取资源

2、撤销进程:撤销死锁进程或代价最小的进程

死锁检测:

1、Jstack

2、Jconsole JDK自带检测

2、线程和进程、协程的区别

进程是程序的一次执行过程,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有 5 种基本状态,初始态,执行态,等待状态,就绪状态,终止状态。

线程是cpu资源分配和调度的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

协程被操作系统内核所管理,而完全是由程序所控制,也就是用户态。协程的开销要远远比线程小。

区别:

  • 进程是系统资源分配和调度的最小单位,线程是cpu资源分配和调度的基本单位。

  • 开销方面: 每个进程都有独立的代码和数据空间(程序上下文),进程之间切换开销大;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

  • 系统为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源

  • 进程中至少包含一个线程

线程创建方式

  1. 通过继承Thread创建线程

    1. 定义Thread类的子类,重写run()方法,run()方法里面是线程的方法体。
    2. 创建子类对象,即线程对象
    3. 线程对象调用start()方法启动线程。
  2. 通过实现Runnable接口创建线程

    1. 定义Runnable接口的实现类,重写run()方法,run()方法里面是线程的方法体。
    2. 创建Runnable实现类对象,依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
    3. 线程对象调用start()方法启动线程。

注意:run()只是线程想要执行的内容,没有返回值。

  1. 通过实现Callable和FutureTask创建线程
    1. 定义Callable接口的实现类,重写call()方法,call()方法里面是线程的方法体,有返回值。
    2. 创建Callable实现类对象,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
    3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。
    4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

注意:call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

线程几种状态

在这里插入图片描述

  • 新建状态(New):
    用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
  • 就绪状态(Runnable):
    当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
  • 运行状态(Running):
    处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。
  • 阻塞状态(Blocked):
    阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。
    1. 等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。

    2. 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。

    3. 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。

  • 死亡状态(Dead):
    当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。

线程方法

  • 1、Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。

  • 2、Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。

  • 3、thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。

  • 4、obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
    obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

  • 5、LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。

3、用户态、内核态

内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。

用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。

所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统, 所以此时程序就需要先操作系统请求以程序的名义来执行这些操作.

用户态切换到内核态的方式

a. 系统调用
b. 异常
c. 外围设备的中断

4、悲观锁、乐观锁

5、线程池

工作原理:1、< corepoolsize 直接加入线程池工作

2、> corepoolsuze 加入队列 ,成功等待,失败 <= maximumpoolsize 进入线程池,> maximunpoolsize 拒绝策略

ThreadPoolExecutor的有哪些参数

corepoolsize, 线程数

MaximumPoolSize,最大线程数

BlockQueue,阻塞队列(

1、无界队列 (LinkedBlockingQueue)一直往后加

2、有界对列  FIFO (ArrayBlockingQueue), 优先对列(PriorityBlockingQueue)

3、同步移交对列 SynchronizedQueue

ArrayBlockingQueue中入队和出队操作,使用同一个lock,所以无法并发。
LinkedBlockingQueue读和写有两把锁ReentrantLock takeLock和putLock,它们之间的操作互相不受干扰,因此两种操作可以并行完成。

LinkedBlockingQueue的吞吐量要高于ArrayBlockingQueue。

handle,队列满后拒绝策略

AbortPolicy                      抛出RejectedExecutionException

DiscardPolicy                  什么也不做,直接忽略

DiscardOldestPolicy       丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置

CallerRunsPolicy             直接由提交任务者执行这个任务

keepAliveTime,空闲线程等待时间

unit,时间单位

threadFactory 创建线程的工厂类

线程池种类

(single,scheme,fixed,cache),初始化线程种类、拒绝策略、线程池执行。

线程池引发的故障到底怎么排查

    jps -l找到项目的进程号(jps -l)

    jstack dump线程信息,如下会将线程信息dump到一个名为thread.txt的文件中(jstack 16555 > thread.txt)

6、进程间通信方式有哪些,优缺点呢

  • 管道
    在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利。

     1,面向字节流
     2,生命周期随内核
     3,自带同步互斥机制
     4,半双工,单向通信,两个管道实现双向通信
    
  • 消息队列
    在内核中创建一队列,队列中每个元素是一个数据报,不同的进程可以通过句柄去访问这个队列。

  • 共享存储
    将同一块物理内存一块映射到不同的进程的虚拟地址空间中,实现不同进程间对同一资源的共享。

  • 信号

  • 信号量
    在内核中创建一个信号量集合(本质是个数组),数组的元素(信号量)都是1,使用P操作进行-1,使用V操作+1

  • 套接字

7、并发与并行?

并发是指多个事件在同一时间段内发生,因此这多个事件是发生在同一实体上的。并发的本质是一个cpu在多个的程序之间进行多路复用,也就是多个用户共享同一个物理资源。

并行是指多个事件在同一时刻发生,因此这多个事件是发生多个实体上。并行的本质是不同的程序不同的cpu同时 运行。

并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。而并发编程可能会遇到很多问题,比如 :内存泄漏、上下文切换、线程安全、死锁等问题。

并发编程三要素:

  • 原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
  • 可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized,volatile)
  • 有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)

(原子性(单一线程)、可见性 (内存强制刷新)、有序性(as-id-serial、happens-before))

出现线程安全问题的原因:

  • 线程切换带来的原子性问题
  • 缓存导致的可见性问题
  • 编译优化带来的有序性问题

解决办法:

  • JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
  • synchronized、volatile、LOCK,可以解决可见性问题
  • Happens-Before 规则可以解决有序性问题

8、多线程

多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。

多线程的好处:

  • 可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

多线程的劣势:

  • 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;

  • 多线程需要协调和管理,所以需要 CPU 时间跟踪线程;

  • 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。

9、线程同步和互斥?

同步就是协同步调,按预定的先后次序进行运行。这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合

线程同步是指多线程 通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步) 也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间是各自运行各自的!

线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。

10、IO网络模型

网络IO的本质是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作

  • 阻塞IO
    blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。没有数据你就一直等着。

  • 非阻塞IO(non-blocking IO)
    在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有,这种方式称为轮询(polling)。。没有数据,直接和我说没有,我等会再来。

  • 多路复用IO(IO multiplexing)
    当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。监视多个连接,谁有数据谁就给我。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

  • select,poll,epoll都是IO多路复用的机制。

  • 在IO多路复用中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的进程其实是一直被block的。只不过进程是被select这个函数block,而不是被socket IO给block。所以IO多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。

  • 异步IO
    用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

同步 I/O:应用进程在调用 recvfrom 操作时会阻塞。
异步 I/O:不会阻塞。

select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。

传统意义上,进程的开销要远远大于线程,所以如果需要同时为较多的客户机提供服务,则不推荐使用多进程;
如果单个服务执行体需要消耗较多的CPU资源,譬如需要进行大规模或长时间的数据运算或文件访问,则进程较为安全

在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程, 即父进程永远无法预测子进程 到底什么时候结束。 当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。

孤儿进程: 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程: 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程控制块(PCB)仍然保存在系统中。这种进程称之为僵尸进程。
  
并发编程面试题(2020最新版)
网络io模型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值