最后
**要想成为高级安卓工程师,必须掌握许多基础的知识。**在工作中,这些原理可以极大的帮助我们理解技术,在面试中,更是可以帮助我们应对大厂面试官的刁难。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
当有多个进程请求资源时,就会造成内存资源紧张,所以操作系统还存在一种挂起操作:将进程交换到外存去,使进程进入挂起状态。
- 活动就绪:进程在内存,处于就绪状态,还需要 。
- 静止就绪:进程在外存,处于就绪状态,还需要调入到内存和 。
- 活动阻塞:进程在内存,但由于某种原因被阻塞了。
- 静止阻塞:进程在外存,但由于某种原因被阻塞了。
进程切换
- 切换页目录,使用新进程的虚拟地址空间。
- 保存当前的 环境、硬件上下文,并导入新进程的的 环境、硬件上下文。
进程和线程的区别
- 一个线程属于一个进程,依赖于进程而存在。一个进程可以包括多个线程,但至少包括一个线程。
- 进程是系统资源分配的最小单位,线程是 调度的最小单位。
- 不同进程在执行过程中拥有独立的内存单元,而同一进程内多个线程共享进程内存。所以一个线程的意外会造成同一进程内其他线程的终止,而进程之间不会互相影响。
- 进程切换时,涉及整个程序的 环境保存和新调度进程的 环境配置。线程切换时,只需要保存少量内容,所以进程切换的系统开销更大。
- 进程通信时,由于它的空间独立性决定了它的通信需要通过操作系统。而线程通信时,由于多线程同享内存导致了线程之间的通信比较容易,不需要通过操作系统。
有了进程为什么还要线程
进程可以提高系统的并发性和资源的利用率,但还是存在一些缺点的:
- 比如一个进程一时间只能做一件事。比如只有进程的话 无法同时完成传输文件、视频聊天。
- 进程在执行过程中被阻塞时,整个进程就会挂起,那么进程中有些不依赖于等待资源的工作也不会执行。比如浏览器想要打印某个页面时,打印机被占用,那么浏览器也无法提供别的服务。
为了解决这些缺点,所以引入了线程作为进程内并发执行的更小单元,从而减少程序在并发执行过程中所付出的时空开销。
进程之间的同步方式
各个进程拥有自己独立的内存空间,为了保证安全,一个进程不可以直接访问另一个进程的内存空间。但进程之间的通信是必不可少的,所以有以下方式完成进程之间的通信:
- 管道通信
- 管道通信分为普通管道和命名管道。普通管道可用于有亲缘关系进程之间的通信,命名管道还允许无亲缘关系进程之间的通信。
- 管道上数据是单方向传输的,想要完成双向通信需要两个管道。
- 消息
- 将通信的数据封装在消息中,通过消息来完成通信。消息的通信方式有两种:
-
直接通信方式,将消息直接发送给对方进程。
-
间接通信方式,双方都通过共享中间实体来完成对消息的法松和接收。
-
信号量
- 信号量本质上是一个计数器,用来完成进程的互斥和同步,比如 操作。
- 共享内存
- 不同进程可以访问同一块内存,共享内存是临界资源。共享内存直接从内存中读取数据,不需要从用户态到内核态的切换,是最快的一种方式。
线程之间的同步方式
- 临界区:多线程访问公共资源,速度快。
- 互斥量:控制多个进程对他们之间共享资源的互斥访问。由于资源只有一个,所以不能被多个线程同时访问。
- 信号量:允许多个线程访问同一资源,但同一时刻访问该资源的线程有最大的数目限制。
线程之间哪些是共享的
- 堆区,堆是进程开辟出来的,多线程共享这部分资源。
- 全局变量和静态变量,和特定线程无关,所以也是共享的。
- 文件等公共资源,使用这些公共资源的线程必须同步。
线程需要保存哪些上下文
线程在切换过程中,需要保存当前线程 、线程状态、堆栈、寄存器状态等信息。寄存器状态主要包括:
- :堆栈指针,指向当前栈的栈顶指针。
- :程序计数器,存储下一跳将要执行的指令。
- :累加寄存器,用于加法乘法的缺省寄存器。
游戏服务器应该为每个用户开辟一个线程还是一个进程
进程。因为同一进程内的线程会相互影响,所以如果一个用户的线程死掉了,其他用户的游戏也会崩溃。所以应该为每个用户开辟一个进程,使用户之间不会相互影响。
多进程和多线程的使用场景
多线程模型适用于 密集型场合。因为经常会因为 阻塞来切换线程,而线程切换的系统开销比进程切换小。
多进程模型适用于需要频繁计算的场合。
多线程是不是越多越好
并不是
- 因为多线程意味着需要更多的内存资源。
- 并不是同时运行多个线程,而是轮流执行了,如果线程过多, 就要在不同的线程之间快速切换,那么 的利用率就会降低。
++ 线程中锁机制
- 互斥锁。互斥锁用于控制多个线程对他们之间共享资源的互斥访问的一个信号量。避免了多个对象同时访问一个共享资源。此时它会被放到等待队列里,然后去处理其他任务。
- 条件锁。当共享数据达到某个值时,唤醒正在等待这个数据的线程,若没有共享数据分配时,向申请的线程挂起。
- 自旋锁。当一个线程想要请求一个资源,但这个资源被别的线程占用时,它会一直处于请求状态而不能去做其他事情(忙等),所以自旋锁容易造成死锁。只有内核是可抢占式的且当临界区资源很快可以被分配到的时候,才考虑采用自旋锁。
- 读写锁。分为读锁和写锁。允许多个线程同时进行读操作,但同一时间只允许一个线程进行写操作。
单核机器上写多线程程序,是否需要考虑加锁
仍然需要线程锁。
线程锁通常用来实现线程的同步和通信,在单核机器上仍然存在线程同步的问题。在抢占式操作系统中,通常为每个线程分配一个时间片,当某个线程时间片耗尽以后,操作系统会把它挂起,然后运行另一个线程。如果两个线程共享某些数据,但没有线程锁,可能会导致同享数据修改引起冲突。
死锁发生的条件以及如何解决死锁
死锁是指两个或者两个以上进程在执行过程中,因为争夺资源而造成相互等待的现象。
四个发生条件分别为:
- 互斥条件。进程分配到的资源不允许其他进程同时访问。若其他进程想要访问该资源,只能等待到占用资源的进程使用完。
- 请求和保持条件。进程获得一定资源后,又对其他资源发出请求。如果其他资源被占用,此时请求阻塞,但该进程不会释放以有的资源。
- 不可剥夺条件。进程获得的资源在使用完成之前不可被剥夺,只能自己使用完后释放。
- 循环等待条件。发生死锁时,一定存在一个 进程——资源 的循环链。
解决死锁的方法:
- 破坏请求和保持条件。在进程开始运行之前,必须一次性分配该进程需要的所有资源。
- 破坏不可剥夺条件。当一个进程对新资源的请求又不能被满足时,必须释放已经获得的所有资源。
- 破坏循环等待条件。系统对所有资源类型进行编号,每个进程必须按序号递增的顺序请求资源。如果想要请求序号较低的资源,必须释放已经获得的高序号的资源。
虚拟内存技术
传统存储器存在问题:当有的作业很大或同一时间有大量作业要求运行时,其需要的内存空间超过了内存总容量,作业无法全部装入,导致作业无法运行。这都是由于传统存储器要求一次性装入作业导致的,所以采用了虚拟内存。
虚拟内存技术使进程在运行过程中,内存中只装入了当前要运行的少数页面,其余部分暂存在外存上。如果程序访问的页尚未调入内存中,便发出缺页中断, 将需要的页调入内存。如果内存满了,无法装入新的页时,便会使用页面置换方式将暂时不用的页调至外存,再将要访问的页调入内存。
虚拟内存的优点:
- 可以更加高效的使用物理内存。
- 使内存的管理更加便捷。在编译程序的时候使用虚拟地址,就不会因为物理地址有时被占用而需要重新编译了。
- 更加安全。每个进程运行在各自的虚拟内存地址空间中,互相不干扰对方。
虚拟内存的缺点:
- 虚拟内存需要建立额外的数据结构,需要占用额外的内存。
- 虚拟地址到物理地址的转换增大的运行时间。
- 页面的换入换出需要磁盘 ,需要耗费很大的时间。
虚拟内存和物理内存怎么对应
请求分页存储管理中一般使用二级页表。
- 从 中取出页表地址。
- 根据地址前十位,找到对应的索引项。此时获得页目录的地址,而不是页的地址。
- 根据地址中间十位,从页表中获得该页的起始地址。
- 将获得的页的起始地址和最后 位地址相加,获得想要的物理地址。
操作系统中的缺页中断
通过 分配内存时,只是分配了虚拟内存而不是实际的物理地址,进程访问时也是访问的虚拟地址而不是物理地址。
在请求分页系统中,可以查询页表的状态来确定要访问的页表是否在内存中。每当要访问的页面不存在内存中时,就会发生一个缺页中断,然后操作系统会将缺失的页调入到内存中。
缺页中断的处理一般分为 个步骤:
- 保护 现场
- 分析中断原因
- 转入缺页中断处理程序进行处理
- 恢复 现场并继续处理
虚拟内存置换的方式
当访问一个不存在内存中的页时,需要从外存调入。如果此时内存已满,就需要调出一个页到外存,在将需要的页调入。这个过程叫做缺页置换。
- 最佳置换算法:调出的页面是未来不访问或最久不访问的页面,但由于实际过程中无法预知未来,这是一种理论的算法。
- 先进先出 页面置换算法:置换掉最早调入内存的页面,也就是说在内存中按队列的形式管理页,从队尾插入,从队首删除。
- 最近最久未使用 置换算法:置换掉最近一段时间内最久未访问的页面。根据局部性原理,刚刚被访问过的页面可能马上又要被访问,而较长时间未访问的页面可能最近不会访问。
- 最少使用 置换算法。置换掉最近一段时间访问次数最少的页面。
- 置换算法。为每一页设置一个访问位,再将页面设置成循环队列。在选择一个页面时,如果访问位是 ,就把它置换掉,如果是 ,就把访问位置为 并开始检查下一个页面。
为什么要有
是 的高速缓存,可以加快读取数据的速度。
在页式存储结构中,需要先访问内存获得数据物理地址,然后再去物理地址中读出数据。但访问内存的速度比较慢,所有引入了 ,访问一个地址时,先去 中查找,如果命中了就可以直接获得其物理地址,然后访问数据,这样可以大大增加访问速度。
种 模型
- 阻塞 。一直检查 事件是否就绪,没有就继续等待,期间什么事也不做。
- 非阻塞 。每隔一段时间检查一下 事件是否就绪,没有就绪就做其他事。
- 信号驱动 。安装一个信号处理函数,进程继续运行。当 事件就绪时,进程会收到 信号,然后处理 事件。
- 多路复用。 多路复用在阻塞 上多了个 函数, 函数可以看后面。
- 异步 。应用程序把 请求给内核后,由内核去完成相关操作。当内核完成相关操作后,会发信号告诉应用进程本次 已经完成。
水平触发和边缘触发
- 水平触发(状态达到):当被监控的文件描述符上有可读写事件发生时,会通知用户程序去读写。如果用户一次读写没取完数据,他会一直通知用户。如果这个描述符是用户不关心的,它每次都返回通知用户,则会导致用户对于关心的描述符的处理效率降低。
- 边缘触发(状态变化):当被监控的文件描述符上有可读写事件发生时,会通知用户程序去读写,它只会通知用户进程一次,这需要用户一次把内容读取完,相对于水平触发,效率更高。如果用户一次没有读完数据,再次请求时,不会立即返回,需要等待下一次的新的数据到来时才会返回,这次返回的内容包括上次未取完的数据。
和 是水平触发的。 支持水平触发也支持边缘触发,但默认是水平触发的。
消息机制
当用户使用键盘或者鼠标时,系统会把这些操作转化成消息。系统会将这些消息放入消息队列中,然后对应的进程会循环从消息队列中取出消息,完成对应的操作。
僵尸进程
- 正常进程
- 正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。但子进程的结束和父进程的运行是一个异步过程,也就是说父进程无法预知子进程什么时候结束。一个进程完成他的工作后,它的父进程要调用 函数来收集子进程的终止状态,并把他彻底销毁后返回,如果没有等到这样的一个子进程,就会阻塞在这里等待。
- 孤儿进程
- 如果一个父进程推出,而他的子进程还在运行,那么这些子进程就变成了孤儿进程。孤儿进程会由 进程收养。
- 僵尸进程
- 如果子进程退出,而父进程没有使用 函数,那么这些进程的进程描述符仍然保存在系统中,这些进程被称为僵尸进程。
- 僵尸进程是一个必经的阶段,如果子进程退出,父进程还没有使用 函数,此时就是僵尸进程,等到父进程处理以后才消失。如果父进程在子进程结束之前退出,子进程就会由 进程接管, 进程会以父进程的身份处理僵尸进程。
僵尸进程的危害:
- 僵尸进程如果不被释放,就会一直占用系统的进程号。而系统的进程号是有限的,如果有大量的僵尸进程,可用的进程就会减少。
如何避免僵尸进程
-
外部解决
-
通过 消灭产生僵尸进程的进程,那么僵尸进程就变成了孤儿进程,由 进程处理。
-
内部解决
-
子进程退出时向父进程发送信号,父进程接收到信号时,在信号处理中调用 处理僵尸进程
-
两次 :父进程 后马上 ,子进程在 一次后马上 ,孙进程完成父进程中本来要完成的事情,由于是孙进程的父进程已经退出了,它变成了孤儿进程,由 进程处理。
线程池
线程池就是首先创建一些线程,它们的集合称为线程池。线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。线程池可以很好的提高性能。
线程池工作流程
- 初始化线程池、任务队列和工作线程
- 向任务队列中添加任务
- 将等候在条件变量(任务队列上有任务)上的一个线程唤醒并从该任务队列中取出第一个任务给该线程执行
- 等待任务队列中所有任务执行完毕
- 关闭线程池
如果线程的空闲时间过长,就可以考虑缩小线程池。
大端小端以及如何判断大端小端
- 大端:指低字节存储在高地址
- 小端:指低字节存储在低地址
可用通过 来判断系统是大端还是小端,因为 总是从低地址开始存放
int main() {
// freopen(“in”, “r”, stdin);
union T {
int x;
char y;
} t;
t.x = 1;
if(t.y == 1) printf(“xiao duan\n”);
else printf(“da duan\n”);
return 0;
尾声
如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
这里,笔者分享一份从架构哲学的层面来剖析的视频及资料给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《2019-2021字节跳动Android面试历年真题解析》
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
021字节跳动Android面试历年真题解析》**
[外链图片转存中…(img-5NQ9lKE1-1715899000469)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!