目录
- 一:JAVA基础
- 二:计算机网络
- 三:操作系统
- 四:数据库
- 五:设计模式
- 六:Android
- 1.Android四大组件
- 2.Activity的生命周期
- 3.Activity 的四种启动模式、应用场景
- 4.两个Activity 之间跳转
- 5.启动其他应用的Activity
- 6.ActivityManagerService
- 7.Service的生命周期
- 8.Service的两种启动方式
- 9.IntentService与普通Service的区别
- 10.Service保活
- 11.Broadcast的分类
- 12.Broadcast的两种注册形式
- 13.ContentProvider(内容提供商)
- 14.View工作原理
- 14.Android中的事件传递机制
- 15.ANR问题
- 16.子线程禁止访问UI
- 17.Handler的实现原理
- 18.ThreadLocal(本地线程)
- 19.线程池
- 20内存泄漏和内存溢出
- 21.内存泄漏,java是否存在内存泄漏
- 22.Android中线程
- 23.冷启动与热启动
- 24.Android 系统启动流程
- 25.转发(Forward)和重定向(Redirect)的区别
(仅适合面试前复习回顾知识点)
一:JAVA基础
1.四大特性及其含义
- 抽象:对现实世界的事物进行概括,抽象为在计算机虚拟世界中有意义的实体
- 封装:将某事物的属性和行为包装到对象中,构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,并且尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系
- 继承:子类继承父类,不仅可以有父类原有的方法和属性,也可以增加自己的或者重写父类的方法及属性
- 多态:允许不同类的对象对同一消息做出各自的响应
2.状态修饰符
3.JVM和JVM 内存模型
- JVM是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域
- Java 多线程之间是通过共享内存来通信的,每个线程都有自己的本地内存
- 共享变量存放于主内存中,线程会拷贝一份共享变量到本地内存
- volatile 关键字就是给内存模型服务的,用来保证内存可见性和顺序性
4.JVM 内存结构
私有数据区包含:
- 程序计数器: 是当前线程所执行的字节码的行号指示器
- 虚拟机栈: 是Java方法执行的内存模型
- 本地方法栈: 是虚拟机使用到的Native方法服务
共享数据区包含:
1.Java堆:
- 用于存放几乎所有的对象实例和数组;
- 是垃圾收集器管理的主要区域,也被称做“GC;
- 是垃圾收集器管理的主要区域,也被称做“GC堆”;
2.方法区
- 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
- 用于存放编译期生成的各种字面量和符号引用
5.JVM之GC
- 回收区域:只针对堆、方法区;线程私有区域数据会随线程结束销毁,不用回收
- 分代收集 GC 方法会吧堆划分为新生代、老年代 新生代:
新生代:新建小对象会进入新生代;通过复制算法回收对象
老年代:新建大对象及老对象会进入老年代;通过标记-清除算法回收对象 - 判断一个对象是否可被回收:
引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
可达性分析法:通过一系列被称为『GC Roots』的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。 - 回收算法
复制算法:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另外一块上面,再将这一块内存空间一次清理掉。
标记-清除算法:首先『标记』出所有需要回收的对象,然后统一『清除』所有被标记的对象。
标记-整理算法:首先『标记』出所有需要回收的对象,然后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。 - 四种引用
强引用:不会被回收
软引用:内存不足时会被回收
弱引用:gc 时会被回收
虚引用:无法通过虚引用得到对象,可以监听对象的回收~
关于G1:
G1是一种服务端应用使用的垃圾收集器,目标是用在多核、大内存的机器上,它在大多数情况下可以实现指定的GC暂停时间,同时还能保持较高的吞吐量。
在G1提出之前,经典的垃圾收集器主要有三种类型:
- 串行收集器
- 并行收集器
- 并发标记清除收集器
这三种收集器分别可以是满足Java应用三种不同的需求:
- 内存占用及并发开销最小化
- 应用吞吐量最大化
- 应用GC暂停时间最小化
但是,上述三种垃圾收集器都有几个共同的问题:
- 所有针对老年代的操作必须扫描整个老年代空间
- 新生代和老年代是独立的连续的内存块,必须先决定年轻代和老年代在虚拟地址空间的位置
G1适用于以下几种应用:
- 可以像CMS收集器一样,允许垃圾收集线程和应用线程并行执行,即需要额外的CPU资源;
- 压缩空闲空间不会延长GC的暂停时间;
- 需要更易预测的GC暂停时间;
- 不需要实现很高的吞吐量。
6.类加载过程,类加载时机,类加载器,双亲委托模型
类加载过程
- 加载:获取类的二进制字节流;生成方法区的运行时存储结构;在内存中生成 Class 对象;
- 验证:确保该 Class 字节流符合虚拟机要求;
- 准备:初始化静态变量;
- 解析:将常量池的符号引用替换为直接引用;
- 初始化:执行静态块代码、类变量赋值;
类加载时机
- 实例化对象
- 调用类的静态方法
- 调用类的静态变量(放入常量池的常量除外)
类加载器
- 引导类加载器 - 没有父类加载器
- 拓展类加载器 - 继承自引导类加载器
- 系统类加载器 - 继承自拓展类加载器
双亲委托模型
- 双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。
- 优点:防止重复加载,父加载器加载过了就没必要加载了;安全,防止篡改核心库类
7.Java中堆和栈
- 栈:主要用来存放基本数据类型和局部变量;当在代码块定义一个变量时会在栈中为这个变量分配内存空间,当超过变量的作用域后这块空间就会被自动释放掉。
- 堆:用来存放运行时创建的对象,比如通过new关键字创建出来的对象和数组;需要由Java虚拟机的自动垃圾回收器来管理。
8.重载和重写
- 重写:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
- 重载:重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同.
9.volatile 关键字
- 只能用来修饰变量,适用修饰可能被多线程同时访问的变量
- 相当于轻量级的 synchronized,volatitle 能保证有序性(禁用指令重排序)、可见性;后者还能保证原子性
- 变量位于主内存中,每个线程还有自己的工作内存,变量在自己线程的工作内存中有份拷贝,线程直接操作的是这个拷贝
- 被 volatile 修饰的变量改变后会立即同步到主内存,保持变量的可见性。
10.内部类
内部类就是定义在另外一个类里面的类。它隐藏在外部类中,封装性更强,不允许除外部类外的其他类访问它;但它可直接访问外部类的成员。
- 静态内部类是指被声明为static的内部类,可不依赖外部类实例化;而非静态内部类需要通过生成外部类来间接生成
- 静态内部类只能访问外部类的静态成员变量和静态方法,而非静态内部类由于持有对外部类的引用,可以访问外部类的所用成员
11.Java集合
Connection接口:单列集合的根接口,它表示一组对象,这些对象也称为Collection的元素
Map接口:双列集合的根接口,Map集合可以存储一对对象,即会一次性保存两个对象,存在key = value 结构,其最大的特点还是可以通过key 找到对应的value 值。
Connection接口: List: 有序,可重复
-
ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高 -
Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低 -
LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
Connection接口: Set : 无序,唯一
-
HashSet
底层数据结构是哈希表。(无序,唯一)
如何来保证元素唯一性?
依赖两个方法:hashCode()和equals() -
LinkedHashSet
底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
1.由链表保证元素有序
2.由哈希表保证元素唯一 -
TreeSet
底层数据结构是红黑树。(唯一,有序)
1 如何保证元素排序的呢?
自然排序
比较器排序
2 如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定
Map接口: HashMap、TreeMap、HashTable
- TreeMap是有序的,HashMap和HashTable是无序的。
- Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。
- Hashtable是线程安全的,HashMap不是线程安全的。
- HashMap效率较高,Hashtable效率较低。
- Hashtable不允许null值,HashMap允许null值(key和value都允许)
集合的选择
1.单列还是双列?
- 单列:Collection类型的
- 双列;Map类型
2.选择单列后看元素是否唯一
-
是:选择Set集合
看元素是否排序 ?
- 是:TreeSet
- 否:HashSet
-
否: 选择List集合
安全线高低 ?
-
高:Vector
-
低:arrayList 或LinkedList
- 增删多:LinkedList
- 查询多:ArrayList
-
12.HashMap是什么,怎么扩容,put,get元素的过程
- HashMap是使用hash算法,然后基于数组+链表+红黑树来实现的,或许还知道HashMap内部数组的初始长度为16,并且还能自动扩容。当链表长度超过8时,将链表转换为红黑树,这样大大减少了查找时间。(为什么不直接红黑树:平衡需要时间)
- HashMap是无序的,而LinkedHashMap是有序的HashMap,默认为插入顺序,还可以是访问顺序,基本原理是其内部通过Entry维护了一个双向链表,负责维护Map的迭代顺序
- HashMap几个默认值,初始容量为16、填充因子默认为0.75、扩容时容量翻倍。也就是说当HashMap中元素个数超过160.75=12时会把数组的大小扩展为216=32,然后重新计算每个元素在数组中的位置
- 向Hashmap中put元素时,首先判断key是否为空,为空则直接调用putForNullKey(),不为空则计算key的hash值得到该元素在数组中的下标值;如果数组在该位置处没有元素,就直接保存;如果有,还要比较是否存在相同的key,存在的话就覆盖原来key的value,否则将该元素保存在链头,先保存的在链尾
- 从Hashmap中get元素时,计算key的hash值找到在数组中的对应的下标值,返回该key对应的value即可,如果有冲突就遍历该位置链表寻找key相同的元素并返回对应的value
红黑树
红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
- 根节点是黑色的
- 叶子节点(特指空节点)是黑色的
- 节点可以是红色的或者黑色的
- 每个红色节点的子节点都是黑色的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
13.各种锁
- 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
- 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。比如Java里面的同步原语synchronized关键字的实现就是悲观锁。
- 独享锁:是指该锁一次只能被一个线程所持有。
- 共享锁:是指该锁可被多个线程所持有。
- 可重入锁:又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
- 公平锁:等待时间最久的线程会优先获得锁,非公平锁无法保证哪个线程获取到锁,synchronized 就是非公平锁
14.Object类的常用方法
- clone():函数的用途是用来另存一个当前存在的对象。
- equale():用于确认两个对象是否相同
- hashCode():用于获取对象的哈希值,这个值的作用是检索
- toString():返回一个String对象,用来标识自己
- getClass():返回一个Class对象
- wait():用于让当前线程失去操作权限,当前线程进入等待序列
- notify():用于随机通知一个持有对象的锁的线程获取操作权限
- notifyAll():用于通知所有持有对象的锁的线程获取操作权限
- wait(long) 和wait(long,int):用于设定下一次获取锁的距离当前释放锁的时间间隔
二:计算机网络
1.网络协议模型
- 物理层:通过媒介传输比特,确定机械及电气规范(比特Bit)
- 数据链路层:将比特组装成帧和点到点的传递(帧Frame)
- 网络层:负责数据包从源到宿的传递和网际互连(包PackeT)
- 传输层:提供端到端的可靠报文传递和错误恢复(段Segment)
- 会话层:建立、管理和终止会话(会话协议数据单元SPDU)
- 表示层:对数据进行翻译、加密和压缩(表示协议数据单元PPDU)
- 应用层:允许访问OSI环境的手段(应用协议数据单元APDU)
2.TCP 和 UDP 区别
- TCP 连接;可靠;有序;面向字节流;速度慢;较重量;全双工;适用于文件传输、浏览器等
- UDP 无连接;不可靠;无序;面向报文;速度快;轻量;适用于即时通讯、视频通话等
3.TCP如何实现可靠性传输,udp如何实现可靠性传输
- TCP使用确认机制、重传机制、滑动窗口。
- UDP传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。
4.三次握手,四次挥手
三次握手:
- A:请求
- B:收到,Over
- A:Over
- A 和 B 两方都要能确保连接,所以需要三次握手。
- 第三次握手可以避免由于客户端延迟的请求连接的请求,使得服务端无故再次建立连接,所以不能两次握手
四次挥手:
- A:结束
- B:稍等片刻,A完成B未必完成
- B:Over
- A:Over
- 由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭。
- 先关读,后关写”,一共需要四个阶段:服务器读通道关闭->客户机写通道关闭->客户机读通道关闭->服务器写通道关闭。
5.MAC和IP地址
- 每台主机在出厂时都有一个唯一的MAC地址,但是IP地址的分配是根据网络的拓朴结构,得以保证路由选择方案建立在网络所处的拓扑位置基础而不是设备制造商的基础上
- 使用IP地址更方便数据传输。数据包在这些节点之间的移动都是由ARP协议负责将IP地址映射到MAC地址上来完成的。
6.拥塞控制
对网络中的路由和链路传输进行速度限制,避免网络过载,包含四个过程:慢开始、拥塞避免、快重传和快恢复
- 慢开始:假设当前发送方拥塞窗口cwnd的值为1,而发送窗口swnd等于拥塞窗口cwnd,因此发送方当前只能发送一个数据报文段(拥塞窗口cwnd的值是几,就能发送几个数据报文段),接收方收到该数据报文段后,给发送方回复一个确认报文段,发送方收到该确认报文后,将拥塞窗口的值变为2,当前的拥塞窗口cwnd的值已经等于慢开始门限值,之后改用拥塞避免算法。
- 拥塞避免:也就是每个传输轮次,拥塞窗口cwnd只能线性加一,而不是像慢开始算法时,每个传输轮次,拥塞窗口cwnd按指数增长。同理,16+1……直至到达24,假设24个报文段在传输过程中丢失4个,接收方只收到20个报文段,给发送方依次回复20个确认报文段,一段时间后,丢失的4个报文段的重传计时器超时了,发送发判断可能出现拥塞,更改cwnd和ssthresh.并重新开始慢开始算法
- 快重传:要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认ACK就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器RTO超时。
- 快恢复:与快速重传配合使用,有以下两个要点:
①当发送方连续收到三个重复确认时,就执行“乘法减小”算法,即把ssthresh门限减半。但是接下去并不执行慢开始算法。
②考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。
7.从输入网址到获得页面的过程
- 浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址
- 解析出IP地址后,根据该IP地址和默认端口80,和服务器建立TCP连接
- 浏览器发出读取文件的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器
- 服务器对浏览器请求作出响应,并把对应的html文本发送给浏览器
- 释放TCP连接,若connection模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
- 客户端将服务器响应的html文本解析并显示
8.Http和Https
- Https是ssl加密传输,Http是明文传输
- Https是使用端口443,而Http使用80
- HttpsSSL+HTTP协议构建的可进行加密传输、身份认证的网络协议要比Http协议安全
- Https协议需要到CA申请证书
常用的HTTP方法有哪些:
- GET: 用于请求访问已经被URI(统一资源标识符)识别的资源,可以通过URL传参给服务
- POST:用于传输信息给服务器,主要功能与GET方法类似,但一般推荐使用POST方式。
- PUT: 传输文件,报文主体中包含文件内容,保存到对应URI位置。
- HEAD: 获得报文首部,与GET方法类似,只是不返回报文主体,一般用于验证URI是否有
- DELETE:删除文件,与PUT方法相反,删除对应URI位置的文件。
- OPTIONS:查询相应URI支持的HTTP方法。
HTTPS工作原理:
- 一、首先HTTP请求服务端生成证书,客户端对证书的有效期、合法性、域名是否与请求的域名一致、证书的公钥(RSA加密)等进行校验;
- 二、客户端如果校验通过后,就根据证书的公钥的有效, 生成随机数,随机数使用公钥进行加密(RSA加密);
- 三、消息体产生的后,对它的摘要进行MD5(或者SHA1)算法加密,此时就得到了RSA签名;
- 四、发送给服务端,此时只有服务端(RSA私钥)能解密。
- 五、解密得到的随机数,再用AES加密,作为密钥(此时的密钥只有客户端和服务端知道)。
三:操作系统
0.基础概述
操作系统的四个特性:
- 并发: 同一段时间内多个程序执行(注意区别并行和并发,前者是同一时刻的多个事件,后者是同一时间段内的多个事件)
- 共享: 系统中的资源可以被内存中多个并发执行的进线程共同使用
- 虚拟: 通过时分复用(如分时系统)以及空分复用(如虚拟内存)技术实现把一个物理实体虚拟为多个
- 异步: 系统中的进程是以走走停停的方式执行的,且以一种不可预知的速度推进
操作系统的主要功能:
- 处理机管理: 处理机分配都是以进程为单位,所以处理机管理也被看做是进程管理。包括进程控制,进程同步,进程通信和进程调度
- 内存管理: 内存分配,内存保护,地址映射,内存扩充
- 设备管理: 管理所有外围设备,包括完成用户的IO请求;为用户进程分配IO设备;提高IO设备利用率;提高IO速度;方便IO的使用
- 文件管理: 管理用户文件和系统文件,方便使用同时保证安全性。包括:磁盘存储空间管理,目录管理,文件读写管理以及文件共享和保护
- 提供用户接口: 程序接口(如API)和用户接口(如GUI)
1.进程和线程
进程
- 进程就是运行起来的程序,程序运行起来需要被加载到内存中。(这是站在用户的角度看待进程的)
- 进程就是操作系统的描述,这个描述叫PCB(进程控制块),
- 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
- 在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
线程
- 线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
- 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
区别
-
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
-
线程 是进程的一个实体, 是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.
- 线程 自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
- 线程 只是一个进程中的不同执行路径,不同的线程互相是可见的,可以共享资源
-
主要差别:在于它们是不同的操作系统资源管理方式。
- 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响
- 线程 只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序 健壮
- 进程切换时,耗费资源较大,效率要差一些
有了进程为什么还要线程?
- 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
- 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
2.内存和虚拟内存
内存
内存:对于用户来说就是一个字节数组,我们可以根据地址来访问到某个字节或者某些字节:
最开始一台机器上只放置一个程序,操作系统仅仅作为一个函数库存在。
后来,人们觉得同时在一台计算机上只运行一个程序太亏了,就设计了一个可以同时运行多个程序的机制。不过内存条只有一个,所以这些用户程序只能共享同一个内存条,只能把内存的不同部分划分给不同的用户程序,画个图就像是这样:
但是也有一些问题:
- 不同用户程序只能使用给他们规定好的那部分内存,也就是程序员在敲代码的时候就应该小心翼翼的计算自己使用的内存
- 能使用的内存空间都给规定好了
- 存在进程恶意其他进程内存或者更新其他进程内存
虚拟内存
为了解决内存存在的一些问题,比如说:
用户直接去操作内存的话,用户也不知道其他人到底使用了内存的哪些字节,自己可以使用哪些字节,每次使用需要查询内存的使用情况太麻烦。
干脆就不让用户直接操作内存了,让用户在编程序的时候直接把内存想象成一个非常非常大的字节数组就好了,他们把这个非常大的字节数组称之为虚拟内存
由操作系统完成从虚拟内存的虚拟地址到真实内存的真实地址之间的映射工作。画个图就像这样:
这样还有问题,用户越来越多,即使每个用户都使用非常少的内存空间,那加起来占用的内存空间都可能超过了真实内存的大小,于是引用了硬盘:
操作系统完成由虚拟内存地址到真实内存地址或者磁盘地址之间的映射工作,
这样子给用户提供的虚拟内存的地址空间就可以非常非常大,用户程序中那些很久都用不到的内存空间可以被操作系统给搞到磁盘上边存储,什么时候需要用了,又从磁盘中加载到真实内存中,重要的是这个过程全部是操作系统自动完成的!
虚拟内存结构
- 用户需要调用函数的时候,一般都会生成一个称之为栈帧的结构,从而导致虚拟内存中的栈空间增长。
- 用户需要单独申请内存空间时,就会从堆空间分配,从而导致虚拟内存中的堆空间增长。
3.创建线程的方式
<1> 继承Thread类重写run方法
public class ThreadB implements Runnable
{
public void run()
{
}
public static void main(String[] args)
{
}
}
<2> 实现Runnable接口方式
public static void main(String[] args)
{
Thread thread = new Thread(new Runnable()
{
public void run()
{
}
}
}
<3> 使用匿名内部类方式
<4> 使用线程池创建线程
4.死锁的产生和避免
- 死锁是指多个进程因循环等待资源而造成无法执行的现象,它会造成进程无法执行,同时会造成系统资源的极大浪费。
- 死锁的产生条件:互斥,占有且等待,不可抢占,循环等待
- 死锁的解决策略:1.银行家算法 2.鸵鸟算法
- 死锁的避免:通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,即“如果一个进程的当前请求的资源会导致死锁,系统拒绝启动该进程;如果一个资源的分配会导致下一步的死锁,系统就拒绝本次的分配”
5.临界区
- 每个进程中访问临界资源的那段程序称为临界区,每次只准许一个进程进入临界区,进入后不允许其他进程进入
- 如果有若干个进程要求进入空闲的临界区,一次仅允许一个进程进入
- 任何时候,处于临界区的进程不可多于一个
- 如已有进程进入自己的临界区,则其他试图进入临界区的进程必须等待
- 进入临界区的进程要在有限时间内退出,以便其他进程能及时进入自己的临界区
- 如果不能进入自己的临界区,就应该让出CPU,避免进程出现忙等等现象
6.进程间的通信方式
- 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在有血缘关系的进程间使用,进程的血缘关系通常是指父子进程关系。
- 命名管道(named pipe):也是半双工的通信方式,但是它允许无亲缘关系关系进程间通信。
- 信号(signal):是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。
- 信号量(semophere):信号量是一个计数器,可用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列(message queue):消息队列是由消息组成的链表,存放在内核中,并由消息队列标识符标识。消息队列克服了信号传递消息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存(shared memory):就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量等配合使用,来实现进程间的同步和通信。
- 套接字(socket):套接口也是进程间的通信机制,与其他通信机制不同的是它可用于不同及其间的进程通信。
7.线程间的通信机制
- 锁机制:包括互斥锁、条件变量、读写锁;
互斥锁提供了以排他方式防止数据结构被并发修改的方法。
读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。 - 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
- 信号机制(Signal):****类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
8.进程间同步与互斥的区别,线程同步的方式
- 互斥:指某一个资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的
- 同步:是指在互斥的基础上(大多数情况下),通过其它机制实现访问者对资源的有序访问。大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
同步:体现的是一种协作性。互斥:体现的是排它性。 - 同步机制遵循的原则:
1.空闲让进;
2.忙则等待;
3.有限等待;
4.让权等待; - 线程同步的方式:
-----临界区:通过对多线程的串行化来访问公共资源或者一段代码,速度快,适合控制数据访问。
-----互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以可以保证公共资源不会同时被多个线程访问。
-----信号量:它允许多个线程同一时刻访问同一资源,但是需要限制同一时刻访问此资源的最大线程数目。信号量对象与其他前面几种方法不同,信号允许多个线程同时使用共享资源。
-----事件(信号):通过通知操作的方式来保持多线程的同步,还可以方便实现多线程的优先级比较操作。
9.生产者消费者问题
要理解生产消费者问题,首先应弄清PV操作的含义:PV操作是由P操作原语和V操作原语组成(原语是不可中断的过程)
P操作相当于申请资源,而V操作相当于释放资源。所以要学生记住以下几个关键字:
P操作 -----申请资源
V操作 -----释放资源
例一:在公共电话厅打电话
- 某人要打电话,首先要进行申请,相当于执行一次P操作,申请一个可用资源(电话)
- 某人用完电话,则有空电话腾出,相当于执行一次V操作,释放一个可用资源(电话)
例二:某银行提供1个服务窗口和10个顾客等待座位。顾客到达银行时,若有空座位,则到取号机领取一个号,等待叫号。取号机每次仅允许一个顾客使用。当营业员空闲时,通过叫号选取一位顾客,并为其服务,
semaphore seets=10; //表示空余座位数量的资源信号量,初值为10
semaphore mutex=1; //互斥信号量,初值为1,用于实现对取号机的互斥访问
semaphore custom=0; //表示顾客数量的资源信号量,初值为0
cobegin {
process 顾客i
{
P(seets);
P(mutex);
从取号机获得一个号码;
V(mutex);
V(custom);
等待叫号;
V(seets);
获得服务;
}
process 营业员
{
while(TRUE)
{
P(custom);
叫号;
为顾客服务;
}
}
四:数据库
1.索引
为什么要使用索引?
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
- 可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。
- 帮助服务器避免排序和临时表
- 将随机IO变为顺序IO
- 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
索引这么多优点,为什么不对表中的每一个列创建一个索引呢?
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
索引是如何提高查询速度的?
- 将无序的数据变成相对有序的数据(就像查目录一样)
索引的注意事项?
- 避免 where 子句中对字段施加函数,这会造成无法命中索引。
- 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。
- 将打算加索引的列设置为 NOT NULL ,否则将导致引擎放弃使用索引而进行全表扫描
- 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用
- 在使用 limit offset 查询缓慢时,可以借助索引来提高性能
五:设计模式
1.单例模式
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
简单来说使用单例模式可以带来下面几个好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
饿汉式单例(是在不管你用的用不上,一开始就建立这个单例对象,线程安全):
由于实例优先提供,不存在线程安全问题,但是没有实现懒加载。
public class SingletonThree {
public static SingletonThree singleton = new SingletonThree();
private SingletonThree () { }
public static SingletonThree getSingleton() {
return singleton;
}
}
懒汉式单例(是在你真正用到的时候才去建这个单例对象)
不安全:在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象
//懒汉式实现了懒加载,但是线程不安全,基本不会使用。
public class SingletonOne {
public static SingletonOne singleton;
private SingletonOne() {}
public static SingletonOne getSingleton() {
if (singleton == null)
return new SingletonOne();
return singleton;
}
}
//在第一种的基础上加了个synchronized,保证线程安全,同时实现了懒加载,只是效率不高。
public class SingletonTwo {
public static SingletonTwo singleton;
private SingletonTwo() {}
public static synchronized SingletonTwo getSingleton() {
if (singleton == null)
return new SingletonTwo();
return singleton;
}
}
静态内部类式(静态内部类保证了懒加载,单例实例优先提供又保证了线程安全性,较实用。)
public class SingletonFive {
private static class SingletonHolder {
private static SingletonFive singleton = new SingletonFive();
}
private SingletonFive () {}
public static final SingletonFive getSingleton(){
return SingletonHolder.singleton;
}
}
2.工厂模式
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
关键代码:创建过程在其子类执行。
3.抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
主要解决:主要解决接口选择的问题。
何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
如何解决:在一个产品族里面,定义多个产品。
关键代码:在一个工厂里聚合多个同类产品。
4.观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
六:Android
1.Android四大组件
- Activity :是用户操作的可视化界面;它为用户提供了一个完成操作指令的窗口。当我们创建完毕Activity之后,需要调用setContentView()方法来完成界面的显示;以此来为用户提供交互的入口。在Android App 中只要能看见的几乎都要依托于Activity,所以Activity是在开发中使用最频繁的一种组件。
- Service:是一个没有界面常驻后台的组件,它通常用作在后台处理耗时的逻辑,与Activity一样,它存在自己的生命周期,也需要在AndroidManifest.xml配置相关信息。
- Content Provider:主要用来接收和发送广播
- Broadcast Receiver:使用很少,一般通过contentprovider访问外部APP的内部数据以及自身数据可以被外部访问,是应用程序间非常通用的共享数据的一种方式
2.Activity的生命周期
- onCreate():Activity 正在创建,常做初始化工作,如setContentView界面资源、初始化数据
- onStart(): Activity 正在启动,这时Activity 可见但不在前台,无法和用户交互
- onResume():Activity 获得焦点,此时Activity 可见且在前台并开始活动
- onPause(): Activity 正在停止,可做 数据存储、停止动画等操作
- onStop(): ctivity 即将停止,可做稍微重量级回收工作,如取消网络连接、注销广播接收器等
- onDestroy(): Activity 即将销毁,常做回收工作、资源释放
- onRestart():Activity 重新启动,
3.Activity 的四种启动模式、应用场景
- standard 标准模式: 每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在,此模式的 Activity 默认会进入启动它的 Activity 所属的任务栈中;
- singleTop 栈顶复用模式: 如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时会回调 onNewIntent方法,如果新 Activity 实例已经存在但不在栈顶,那么Activity 依然会被重新创建;
- singleTask 栈内复用模式: 只要 Activity 在一个任务栈中存在,那么多次启动此 Activity 都不会重新创建实例,并回调onNewIntent 方法,此模式启动 Activity A,系统首先会寻找是否存在 A 想要的任务栈,如果不存在,就会重新创建一个任务栈,然后把创建好 A 的实例放到栈中;
- singleInstance单实例模式: 这是一种加强的 singleTask 模式,具有此种模式的 Activity 只能单独地位于一个任务栈中,且此任务栈中只有唯一一个实例;
4.两个Activity 之间跳转
- 首先定义两个Activity,分别为A和B。
- 当我们在A中激活B时,A调用onPause()方法,此时B出现在屏幕时,B调用onCreate()、onStart()、onResume()。
- 这个时候B【B不是一个透明的窗体或对话框的形式】已经覆盖了A的窗体,A会调用onStop()方法
5.启动其他应用的Activity
- 在保证有权限访问的情况下,通过隐式Intent进行目标Activity的IntentFilter匹配
- 一个intent只有同时匹配某个Activity的intent-filter中的action、category、data才算完全匹配,才能启动该Activity。
- 一个Activity可以有多个 intent-filter,一个 intent只要成功匹配任意一组 intent-filter,就可以启动该Activity。
6.ActivityManagerService
- ActivityManagerService是Android中最核心的服务 , 主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块类似。
7.Service的生命周期
- onCreate():服务第一次被创建时调用
- onStartComand():服务启动时调用
- onBind():服务被绑定时调用
- onUnBind():服务被解绑时调用
- onDestroy():服务停止时调用
8.Service的两种启动方式
- Context.startService()方式启动:**通过startService启动后,service会一直无限期运行下去,只有外部调用了stopService()或stopSelf()方法时,该Service才会停止运行并销毁。
生命周期如下所示,启动时,startService->onCreate()->onStart(),停止时,stopService->onDestroy() - Context.bindService()方式启动:bindService启动的服务和调用者之间是典型的client-server模式。调用者是client,service则是server端。service只有一个,但绑定到service上面的client可以有一个或很多个。当多个client都解除绑定之后,系统才会销毁service。
生命周期如下所示,绑定时,bindService -> onCreate() –> onBind(),调用者退出即解绑定时,Srevice就会unbindService –>onUnbind() –> onDestory()。
9.IntentService与普通Service的区别
- 相同点:IntentService继承自Service,因而两个都是服务
- 不同点: IntentService内部开启了一个HandlerThread线程,然后使用此线程的Looper构造了一个Handler对象,在这个线程中执行Handler对象发送的消息。IntentService可以执行耗时任务。普通的Service如果不开启子线程的话是不能执行耗时任务的,会造成ANR。
10.Service保活
- 在Service的onStartCommand()中设置flages值为START_STICKY,使得Service被杀死后尝试再次启动Service
- 提升Service优先级,比如设置为一个前台服务
- 在Activity的onDestroy()通过发送广播,并在广播接收器的onReceive()中启动Service
11.Broadcast的分类
- Normal Broadcast:一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们接收的先后是随机的。
- Ordered Broadcast:一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,所以此时的广播接收器是有先后顺序的,且优先级(priority)高的广播接收器会先收到广播消息。有序广播可以被接收器截断使得后面的接收器无法收到它。
- System Broadcast:Android系统内置了多个广播,涉及到手机的基本操作,基本上都会发出相应的系统广播。
- Local Broadcast:发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收本应用程序发出的广播。
- Sticky Broadcast:这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播。
12.Broadcast的两种注册形式
- 代码动态注册:动态注册的接收器必须要在程序启动之后才能接收到广播
- 配置文件里静态注册:态注册的接收器即便程序未启动也能接收到广播,比如想接收到手机开机完成后系统发出的广播
13.ContentProvider(内容提供商)
- ContentProvider的作用是为不同的应用之间数据共享,提供统一的接口,我们知道安卓系统中应用内部的数据是对外隔离的,要想让其它应用能使用自己的数据(例如通讯录)。
- 封装。对数据进行封装,提供统一的接口,使用者完全不必关心这些数据是在 DB ,XML 、Preferences 或者网络请求来的。当项目需求要改变数据来源时,使用我们的地方完全不需要修改。
- 提供一种跨进程数据共享的方式。
- 数据更新通知机制,数据是在多个应用程序中共享的,当其中一个应用改变了这些共享数据的时候,它有责任通知其它应用程序,让它们知道共享数据被修改了,这样它们就可以作相应的处理。
14.View工作原理
ViewRoot 的 performTraversals 方法调用触发开始 View 的绘制,然后会依次调用
- 先measure测量,用于确定View的测量宽高,再 layout布局,用于确定View的最终宽高和四个顶点的位置,最后 draw绘制,用于将View 绘制到屏幕上
- performMeasure:遍历 View 的 measure 测量尺寸
- performLayout:遍历 View 的 layout 确定位置
- performDraw:遍历 View 的 draw 绘制
14.Android中的事件传递机制
- 一个MotionEvent产生了以后,系统需要将这个点击事件传递到一个具体的View上。
- 事件的传递顺序:Activity(Window) -> ViewGroup -> View
- 用户触摸该位置时候,最先调用最外层父控件ViewGroupA的 dispatchTouchEvent(),然后调用ViewGroupA的onInterceptTouchEvent(),如果返回true,表示拦截事件,此时直接调用 ViewGroupA的 onTouchEvent(),如果返回false,就把事件继续给父控件 ViewGroupB分发;
- 首先调用 父控件ViewGroupB的 dispatchTouchEvent(),然后调用 ViewGroupB的 onInterceptTouchEvent(),如果返回true,表示拦截事件,直接调用 ViewGroupB的 onTouchEvent(),如果返回false,表示不拦截,继续把事件分发给下一级View;
15.ANR问题
ANR(Application Not responding)。Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR。
- 在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸
- BroadcastReceiver在10秒内没有执行完毕
- service 前台20s后台200s未完成启动 Timeout executing service
- app自身进程主线程阻塞, 挂起, 死锁导致
- 机器本身的cpu, 内存, io繁忙, 无法及时响应
ANR异常是经常遇到的问题,主要的解决办法最常用的就是不要在主线程中做耗时的操作,而应放在子线程中来实现
16.子线程禁止访问UI
- UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态
- UI是属于主线程的,子线程要想用,不好意思资源只有一份,子线程抢不过主线程(也就是所谓的避免死锁)
- Android是单线程模型
为什么不加锁?
- 锁机制会让UI访问的逻辑变得复杂
- 锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行
17.Handler的实现原理
- 当我们需要在子线程处理耗时的操作(例如访问网络,数据库的操作),而当耗时的操作完成后,需要更新UI,这就需要使用Handler来处理,因为子线程不能做更新UI的操作,Handler能帮我们很容易的把任务(在子线程处理)切换回它所在的线程。简单理解,Handler就是解决线程和线程之间的通信的。
- Handler有几个核心的类:Message,Handler,Message Queue,Looper和ThreadLocal
- 一个Thread只能有一个Looper,可以有多个Handler
- Looper有一个MessageQueue,可以处理来自多个Handler的Message;MessageQueue有一组待处理的Message,这些Message可来自不同的Handler;Message中记录了负责发送和处理消息的Handler;Handler中有Looper和MessageQueue
- 主线程直接new一个Handler,子线程的Looper需要手动去创建`
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//为子线程创建Looper
new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//子线程消息处理
}
};
Looper.loop();
}
}).start();
18.ThreadLocal(本地线程)
- ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
- 解决并发问题:使用 ThreadLocal 代替 Synchronized 来保证线程安全,同步机制采用空间换时间 -> 仅仅先提供一份变量,各个线程轮流访问,后者每个线程都持有一份变量,访问时互不影响。
- 解决数据存储问题: ThreadLocal 为变量在每个线程中创建了一个副本,所以每个线程可以访问自己内部的副本变量。
19.线程池
通过ThreadPoolExecutor并通过一系列参数来配置各种各样的线程池,线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗
当一个任务提交到线程池时
- 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步
- 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步
- 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常
线程池的参数:
- corePoolSize:核心线程数量,会一直存在,除非allowCoreThreadTimeOut设置为true
- maximumPoolSize:线程池允许的最大线程池数量
- keepAliveTime:线程数量超过corePoolSize,空闲线程的最大超时时间
- unit:超时时间的单位
- workQueue:工作队列,保存未执行的Runnable 任务
- threadFactory:创建线程的工厂类
- handler:当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略。
线程池的好处:
- 对多个线程进行统一地管理,避免资源竞争中出现的问题。
- 对线程进行复用,线程在执行完任务后不会立刻销毁,而会等待另外的任务,这样就不会频繁地创建、销毁线程和调用GC。
- JAVA提供了一套完整的ExecutorService线程池创建的api,可创建多种功能不一的线程池,使用起来很方便。
线程池分类
- FixThreadPool:线程数量固定的线程池,所有线程都是核心线程,当线程空闲时不会被回收;能快速响应外界请求。适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器;
- CachedThreadPool:线程数量不定的线程池(最大线程数为Integer.MAX_VALUE),只有非核心线程,空闲线程有超时机制,超时回收;适合于执行大量的耗时较少的任务。适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;
- ScheduledThreadPool:核心线程数量固定,非核心线程数量不定;可进行定时任务和固定周期的任务。适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景;
- SingleThreadExecutor:只有一个核心线程,可确保所有的任务都在同一个线程中按顺序执行;好处是无需处理线程同步问题。适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景;
创建方式:
<1> 使用 Executors 创建
通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用Java提供好的线程池.
<2> ThreadPoolExecutor的构造函数创建
<3> 使用开源类库,如apache和guava等
20内存泄漏和内存溢出
- 内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间。是造成应用程序OOM的主要原因之一
- 内存溢出:是指程序在申请内存时,没有足够的内存空间供其使用。
21.内存泄漏,java是否存在内存泄漏
内存泄漏(Memory Leak)是指程序在申请内存后,无法释放已申请的内存空间。
- 单例模式导致的内存泄漏:单例传入参数this来自Activity,使得持有对Activity的引用。
- Handler导致的内存泄漏:Message持有对Handler的引用,而非静态内部类的Handler又隐式持有对外部类Activity的引用,使得引用关系会保持至消息得到处理,从而阻止了Activity的回收。
- 线程导致的内存泄漏:AsyncTask/Runnable以匿名内部类的方式存在,会隐式持有对所在Activity的引用。
- 资源未关闭导致的内存泄漏:未及时注销资源导致内存泄漏,如BraodcastReceiver、File、Cursor、Stream、Bitmap等。
22.Android中线程
- AsyncTask:内部是Handler和两个线程池实现的,Handler用于将线程切换到主线程,两个线程池一个用于任务的排队,一个用于执行任务,当AsyncTask执行execute方法时会封装出一个FutureTask对象,将这个对象加入队列中,如果此时没有正在执行的任务,就执行它,执行完成之后继续执行队列中下一个任务,执行完成通过Handler将事件发送到主线程。AsyncTask必须在主线程初始化,因为内部的Handler是一个静态对象,在AsyncTask类加载的时候他就已经被初始化了。
- HandlerThread:继承自 Thread,start开启线程后,会在其run方法中会通过Looper 创建消息队列并开启消息循环,这个消息队列运行在子线程中,所以可以将HandlerThread 中的 Looper 实例传递给一个 Handler,从而保证这个 Handler 的 handleMessage 方法运行在子线程中,Android 中使用 HandlerThread的一个场景就是 IntentService。
- IntentService:继承自Service,它的内部封装了 HandlerThread 和Handler,可以执行耗时任务,同时因为它是一个服务,优先级比普通线程高很多,所以更适合执行一些高优先级的后台任务,HandlerThread底层通过Looper消息队列实现的,所以它是顺序的执行每一个任务。可以通过Intent的方式开启IntentService,IntentService通过handler将每一个intent加入HandlerThread子线程中的消息队列,通过looper按顺序一个个的取出并执行,执行完成后自动结束自己,不需要开发者手动关闭。
23.冷启动与热启动
- 冷启动: 当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用, 这个启动方式就叫做冷启动(后台不存在该应用进程)。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
- 热启动: 当应用已经被打开, 但是被按下返回键、Home键等按键时回到桌面或者是其他程序的时候,再重新打开该app时, 这个方式叫做热启动(后台已经存在该应用进程)。热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application。
24.Android 系统启动流程
- 按电源键
- 加载引导程序 BootLoader 到 RAM
- 执行 BootLoader 程序启动内核
- 启动 init 进程
- 启动 Zygote 和各种守护进程
- 启动 System Server 服务进程开启 AMS、WMS 等
- 启动 Launcher 应用进程
25.转发(Forward)和重定向(Redirect)的区别
转发是服务器行为,重定向是客户端行为。
-
从地址栏显示来说:
forward:是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。
redirect:是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址。所以地址栏显示的是新的URL。 -
从数据共享来说:
forward:转发页面和转发到的页面可以共享request里面的数据。
redirect:不能共享数据。 -
从运用地方来说:
forward:一般用于用户登陆的时候,根据角色转发到相应的模块。
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等。 -
从效率来说:
forward:高。
redirect:低。
困了,未完待续。。。