互联网面试基础知识大全
2020-06-28 10:11:11 5 举报
AI智能生成
计算机java程序员面试整理
作者其他创作
大纲/内容
java
基础
JDK和JRE的区别?
JDK包含了JRE,JRE是java运行时环境,Jdk是开发环境
接口和抽象类
接口是公开的,不能有私有的方法或变量,所有方法都没有方法体,interface实现。
抽象类是可以有私有方法或变量,抽象方法不能包含方法体。
引用传递和按值传递?
引用传递
传递的是内存地址,函数中可以改变原始对象
按值传递
传递的是值的拷贝,函数中无法改变原始对象
java中使用的是?
java中使用的是按值传递的方式;如果参数是基本类型,传递的是值的拷贝;如果参数是引用类型,传递的是对象在内存中地址值的拷贝
基本类型和包装类型
基本类型
1.基本类型中只有数据,且不能为null
2.基本类型用于泛型时,编译会报错
包装类型
1.包装类型包含了数据和操作,可以为null
2.包装类型可用于泛型
泛型
什么是泛型?
泛型实际上实现了将类型参数化的概念,使代码可以应用于多种类型
项目中是如何使用的?
项目中一些通用的工具类或者方法会用到
Object和泛型的区别?
1.Object是所有类型的父类,在强制转换失败时,只有运行时才会报错
2.泛型在编译时就检查类型安全,不需要进行强转
反射
是什么?
当程序在运行时获取一个类的内部信息
为什么?(解决了什么问题)
当程序在运行时,需要动态的加载一些类到jvm中,这些类可能平时用不到,只是在需要时才进行加载.比如项目用的mysql,但有时需要使用oracle,然后就可以通过class.forname()将Oracle的驱动加载到内存中
怎么用?
反射的用途
1.在运行时判断一个对象所属的类
2.在运行时构造一个类的对象
3.在运行时调用一个对象的方法
反射的实现
1.先获取class对象
2.判断是否为某个类的实例
3.创建实例
4.获取方法
5.获取构造器信息
6.获取类的成员变量信息
7.调用invoke方法
8.利用反射创建数组Array.newInstance()
class.forname()的作用
加载一个类到内存中
String为什么不可修改
字符串内部封装了一个被final修饰过的char数组,对于char数组的改变,方法内部都会返回一个新的string实例.
equals和==的区别?
==比较的是两个变量是否指向同一个内存空间.equals判断的是两个变量所指向内存空间的值是否相同
像string中的equals就是重写了Obj.equals方法,对char数组进行遍历来进行比较,如果比较的对象没有对Obj.equals进行重写的话,比较的还是==
final、finally、finalize的区别
final :修饰类,不能被继承,修饰方法和变量,不能被改变
finally:必须执行的部分,配合try/catch使用
finalize;在手动进行垃圾回收时需要重写finalize方法(super.finalize)
Lock锁
读写锁ReentrantReadWriteLock
原理
基于AQS抽象队列实现,java读写锁用state的高16位表示读锁的线程数,低16位表示写锁的重入数.
AQS:AQS是一个同步器,数据结构是双向链表+state(锁状态),底层使用CAS实现
怎么防止新来的读不断占用锁,写抢不到锁?
工作模式
读写锁维护了一对锁,读锁是一个共享锁,写锁是一个排他锁,当线程拥有写锁时,其它线程的读写操作都会被阻塞.当拥有读锁时,其它线程的读操作不会被阻塞,写操作会被阻塞.(为什么写会被阻塞?:防止出现幻读)
可以锁降级吗?
读写锁支持锁降级,写锁可以降级为读锁,需要首先维持住当前的写锁,再获取读锁,随后释放写锁.
乐观锁
介绍
当且仅当比较的新值A与旧值相等时,通过原子方式用新值B替换旧值,否则不执行任何操作
ABA问题
内存值原来是A,后来变成了B,然后又变成了A,CAS在检查的时候会发现并没有变化.
JDK的解决办法
jdk1.5之后提供了AtomicReference类中的compareAndSet来保证引用对象之间的原子性.
syncronized
是什么?
是jvm提供的一个并发控制的内置关键字,实现原理来自于操作系统中进程同步的管程.jvm基于进入和退出Monitor对象来实现同步,代码块同步使用moniter
enter和moniter Exit的指令实现,moniter enter在编译后会被插入到代码块的开始位置,moniter exit插入在代码块
的结束位置或者异常位置,JVM保证每一个enter都有对应的exist与之配对.而关于synchronized使用的锁,是存放在
java对象头中,里面包含了对象的hashCode,锁状态等信息
解决了什么问题?
它可以保证在同一时刻,只有一个线程可以执行某个方法或者某个代码块.
怎么用?
可以用来修饰方法和代码块.
syncronized锁升级
无锁->偏向锁->轻量级锁->重量级锁
无锁
不对资源锁定,所有线程都能更改,但只有一个能更改成功
偏向锁
同一个线程执行同步资源时自动获取资源
偏向锁->轻量级锁
当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁
轻量级锁
多个线程竞争资源时,没有获取资源的线程自旋等待
轻量级锁->重量级锁
自旋等待的线程超过一定数量,或者A持有,B自旋,C访问时,轻量级锁会升级为重量级锁
重量级锁
多个线程竞争资源时,没有获取资源的线程阻塞等待
什么是自旋?
线程在获取锁时,锁已经被其它线程持有,它将循环等待锁释放
syncronized和volatile的区别
内存与锁定 1.volatile表示当前变量在工作内存中的值是不确定的,需要从主内存中读取.而sync则是锁定当前变量,只有当前线程可以访问
修饰 2.volatile只能修饰变量,sync可以修饰方法和代码块
重排 3.volatile修饰的变量不会被优化重排,而sync修饰的则可以.
synchronized和reentrantlock的区别
1.底层实现。sync是jvm实现,reentrantLock是JDK1.5之后API提供的,(需要lock()和unlock()配合try/finally实现;)
2.等待可中断。如果一个持有锁的线程不释放,sync将一直等待,reentrantLock在等待一段时间后仍未获得锁,将会中断执行其它任务
3.非公平锁。sync是非公平锁,在释放锁之后任何一个线程都可以获得锁,reentrantLock默认情况下也是非公平锁,可以通过参数来设置为公平锁.(参数,fair=true)
synchronized和lock区别
加解锁。synchronized 是 Java 关键字,JVM层面 实现加锁和释放锁;Lock 是一个接口,在代码层面实现加锁和释放锁
自动释放锁。synchronized 在线程代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要再 finally {} 代码块显式地中释放锁
是否等待。synchronized 会导致线程拿不到锁一直等待;Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
锁成功是否可知。synchronized 无法得知是否获取锁成功;Lock 可以通过 tryLock 获得加锁是否成功
并发
Copy-On-Write
原理
在往容器中添加数据时,先复制一份出来,往复制出的那一份添加数据,最后将原容器的引用指向新容器.
使用时的注意项
减少扩容开销
根据实际需要,初始化CopyOnWriteArrayList的大小,避免写时CopyOnWriteArrayList扩容的开销。
使用批量添加
因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。
优点
可以进行并发读而不需要加锁
缺点
内存占用问题
因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象。如果这些对象占用的内存比较大,很有可能造成频繁的Full GC
解决方案
针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用copyOnWrite容器,而使用其它的容器,如ConCurrentHashMap等.
数据一致性问题
CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。
多线程
ThreadLocal作用和原理
是什么?
为每一个使用变量的线程都提供一份该变量的副本,每个线程都可以修改自己的副本而不和其它副本冲突.它会将数据放入当前线程对象的Map中,key为当前对象,value是存储的值,每个线程的值只对当前线程可见,当前线程销毁,Map随即销毁,Map中的数据为弱引用,如果没有被引用,会随时被GC回收
解决了什么问题?
[object Object]
解决了多线程资源共享的问题.在同步机制中,通过锁机制保证同一时刻只有一个线程访问变量,这时候变量是共享的,需要考虑上锁和释放等问题,而ThreadLocal为每一个线程都提供了一份该变量的副本,从而隔离了多线程对数据的访问冲突问题
怎么用?
可以用来存用户的登录信息,方便其它地方调用用户信息
ThreadLocal和局部变量的区别?
普通成员变量的初始化对这个线程本身是高度耦合的,必须要知道这个线程是哪个,普通成员变量名是什么才能进行赋值。
而ThreadLocal则不需要,只需要声明好泛型的类型,定义好初始化函数即可。
为什么使用弱引用?
为了防止内存泄露问题的发生,如果设置成强引用的话,会造成jvm垃圾回收器无法回收的问题
run()和start()
run()
run()是Runnable接口下的唯一一个方法,创建多线程需要实现run()方法,在直接调用时是同步执行的.
start()
start()方法是Thread的方法,用来异步启动一个线程,该线程不会马上运行,会放在等待队列等待CPU调度,只有被调度的时候才会调用run()方法执行.
sleep()和wait()
类的区别 sleep是Thread类的方法,wait是Object类中定义的方法
锁的区别 sleep不会导致锁行为的改变,wait会释放锁
cpu唤醒的区别 sleep和wait都会暂停当前的线程,在此期间,CPU会将执行时间分配给其它的线程,不同的是,调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间.
线程池
是什么?
线程池是一种基于池化思想管理线程的工具,java中线程池的核心实现类是ThreadPollExecutor,当execute任务提交时,它会判断该任务会如何处理,是直接执行,还是缓冲执行,或者是拒绝执行.当线程池在运行状态并且线程数小于corePoolSize时,会直接执行该任务.否则会放进阻塞队列中缓冲执行.如果阻塞队列满了的话,它会看是否小于max,如果小于则会直接执行,如果大于等于,最后会执行任务拒绝.
解决了什么问题?
线程池解决的核心问题就是资源管理问题。在并发环境下,系统确定在某一时刻,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来很多问题,比如频繁申请和销毁资源所带来的额外消耗;对资源无限申请缺少抑制手段,引发的系统资源耗尽风险。
怎么用?
快速响应用户请求时,需要并行执行某些任务,比如用户要查看一个设备的信息,我们需要将设备维度的一系列信息如所属机构,设备图片等聚合展示给用户,往往会选择线程池,将调用封装成任务并行执行,缩短总体响应时间.
创建线程池需要传递哪些参数?
核心线程数core,最大线程数max,空闲线程存活时间time,存放任务的队列workQueue
volatile
是什么?
是修饰共享变量的关键字
解决了什么问题?
因为它解决了多线程对变量操作的不可见性和jvm优化时的指令重排序问题.
怎么用?
标志服务状态时可以使用,当为true表示服务开启,false服务关闭这样的场景
对当前变量的++操作可以保证原子性吗?
不可以,它只能保证单个变量的原子性,因为变量的复合操作会导致数据不一致的情况产生.比如说A线程读了该变量但是因为阻塞未进行修改,此时未出发volatile规则,然后B线程也读了该变量并对其进行累加后写入了内存,此时A对其进行的累加是在上次的结果上进行的,导致数据未达到预期,此类问题可以用sync解决或者atomic的CAS(compareAndSet)解决.
JVM
java内存泄漏
长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露。
如何避免内存泄漏?
1.不要在for循环中创建对象
2.不要申请不可预期的内存
3.避免在循环中出现申请内存的操作
4.尽量复用对象,减少运行期间垃圾产生的情况
如何判断一个对象是否可以被回收?
必须同时满足下面三个条件
1.堆中不存在该类的任何实例
2.加载该类的classloader已经被回收
3.该类的class对象没有在任何地方被引用
强软弱虚
强引用
只要关联的对象有强引用,JVM不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象,日常业务代码中99.99%都是强引用,如果要回收一个强引用的话,将该对象置为空,重写finalize方法,调用System.gc进行回收
软引用
只有在内存不足的时候jvm才会回收该对象,如果在内存情况足够的情况下手动进行GC,是不会回收该对象的
弱引用
不管内存是否足够,只要发生GC,就会被回收.
虚引用
当发生GC时,虚引用就会被回收,jvm会把回收的通知放在referenceQueue
集合
HashMap
是什么?
从结构来讲,hashMap是数组+链表+红黑树实现的
当它在插入元素时,会先判断table数组是否为空,如果是空先创建,然后会根据hash值计算数组索引,当索引位置为空时,新建节点添加,如果不为空,分别判断hashcode与key值是否相同,如果都相同会进行覆盖,不相同则会判断当前节点是否为红黑树,如果是红黑树则进行添加,如果不是则会判断当前链表长度是否大于8,大于8会转红黑树并进行插入,小于8的话则进行链表插入,插入完成判断是否超过最大容量(默认长度*0.75),超过会进行扩容.
在它在查询的时候,会根据key进行hash,得到索引位置,equals进一步确定键值对.
子主题 1
怎么用?
适用于单线程情况下,当在多线程情况下进行put时,会使链表上环,进而造成死循环的情况发生,所以在多线程下,应该使用concurrentHashMap.
hashMap是怎么解决hash冲突的?
采用链地址法解决hash冲突,链表中存储的内容是hash相同但key不相同的内容
hashmap1.8为什么选择红黑树,而不选择AVL完全平衡二叉树?
1.AVL树相对于红黑树来讲,查找速度更快,较适合读操作密集型任务
2.选用红黑树的原因是因为AVL树几乎在每次插入时都需要进行左旋或者右旋,因为它的左右子树高度差至多为1.
StringBuffer、StringBuilder的区别
1.都继承自相同的父类AbstractStringBuilder
2.StringBuffer是线程安全的,而Stringbuilder则是非线程安全.
3.在使用字符串拼接的时候,底层使用的是StringBuilder进行拼接的
LinkedList链表为什么是双向?
1.单链表只有一个指向下一结点的指针,也就是只能next
2.双链表除了有一个指向下一结点的指针外,还有一个指向前一结点的指针.
数组扩容怎么实现?
当数组满了的时候,申请一块新的连续地址,将旧数组中的数据copy到新数组中,将要插入的数据放到新数组的末尾
hashmap\treemap\linkedhashmap有序性
hashMap在遍历时,输出结果是无序的
linkedHashMap继承自hashMap,在hashMap的基础上增加了一个链表,用来保存元素的顺序.
linkedHashMap和treeMap的区别是,LinkedHashMap是以元素插入的顺序进行输出,而treeMap则根据元素的key值进行排序
HashMap、HashTable、ConcurrentHashMap的区别?
1.底层实现方面,hashMap和HashTable底层使用数组+链表实现,concurrentHashMap使用分段数组+链表.
2.hashMap允许key和value为null,hashTable不允许
3.线程安全方面,HashTable和concurrentHashMap是线程安全,hashMap是非线程安全.
ps:HashTable线程安全的方式是修改时锁住整个hashTable,concurrentHashMap则是使用segment作分段锁,读操作不加锁,使用volatile实现.
concurrentHashMap
ConcurrentHashMap的加锁
concurrentHashMap加锁流程:
concurrentHashMap是由segment和hashEntry组成,一个concurrentHashMap中包含一个segment数组,一个segment中包含一个hashEntry,每个hashEntry是一个链表结构的元素.一个segment守护一个hashEntry,当需要对hashEntry进行修改时,需要先获得对应的segment锁.
读操作不需要加锁,使用volatile保证node节点的可见性.
解决了什么问题?
1.并发hashMap可能导致死循环
hashMap在并发执行put时会造成链表上环,一旦成环,Entry的next节点用不为空,所以会导致死循环
2.hashTable效率低下
hashTab使用suynconized保证线程安全,当一个线程访问同步方法,另一线程也需要访问是,会进入阻塞或轮询状态.如A在进行put添加元素时,B也不能使用get获取元素.
List
ArrayList
查询速度快,增删慢,线程不安全
底层数据结构是数组,默认大小为10,默认扩展是50%
Vector
查询快,增删慢,线程安全,效率低
默认大小为10,当增量大于0时,可以自定义增量,否则扩大为原来的2倍
LinkedList
双向链表,增删快,查找慢,线程不安全
底层数据结构是链表
数组和链表
数组
数组访问速度快,插入和删除慢,查询快的原因是因为数组在内存中是一块连续的区域,CPU在从内存中往缓存读数据的时候,会进行局部预读,所以访问速度较快.插入和删除慢的原因是因为在操作时元素需要移动大量元素.
链表
链表插入快,访问慢,链表访问速度慢的原因是因为元素在内存中是分散存储的,在查找元素的时候需要从头指针出发寻找,插入快的原因是因为在插入时仅需要修改前一节点的指针域和插入元素的指针域即可.(修改前一节点(a)的指针域令其指向插入元素(x),修改插入元素的指针域令其指向后一元素(b) a->x->b)
Spring
Spring事务
spring事务默认对什么异常回滚?
RuntimeException和Error
源码:public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
编程式事务
通过代码的方式实现事务
通过提供接口PlatformTransactionManager,该接口提供了getTransaction,commit和rollback三个方法,交由第三方厂商进行实现。在我们使用的Mybatis事务配置中,配置的就是该接口的实现类DataSourceTransactionManager
声明式事务
XML元数据驱动
注解元数据驱动
原理
将对应方法的事务代码,通过注解进行标注,在运行期间通过反射读取所标注的代码块,最终为读取的信息构建事务管理的支持
@Teansactional控制事务失效的几种情况?
1.数据库引擎是否支持事务(MyIsam不支持事务)
2.当前类未被spring管理
3.方法不是public的
4.把异常吃了,然后又不抛出来(catch{}中无内容)
spring中Bean的scope
Singleton
默认类型。在第一次创建后会缓存起来,之后不再创建。
Prototype
线程每次调用这个bean都新创建一个实例。
Request
表示每个request作用域内的请求只创建一个实例。
Session
每个session作用域内的请求只创建一个实例。
GlobalSession
这个只在porlet的web应用程序中才有意义,它映射到porlet的global范围的session,如果普通的web应用使用了这个scope,容器会把它作为普通的session作用域的scope创建。
spring bean生命周期?
实例化
属性赋值
初始化
销毁
mybatis中#和$的区别
1.#{}占位符,${}拼接符
2.#{}能防止sql 注入,${}不能防止sql 注入
mybatis只有接口为什么可以运行?
CGlib动态代理
计算机网络
session与cookie的区别
cookie是为会话存储的键值信息,不可跨域名(只能拿到当前域名下的cookie,包含父级域名),有有效期限,是在客户端的浏览器保存。
session是基于内存的缓存技术,用来保存针对每个用户的会话数据,通过session ID 来区分用户,存储于服务器端。
浏览器在第一次请求时,无cookie,然后服务器收到请求后,创建一个 session,用sessionid 标识,将其放入cookie中,然后客户端以后请求都带上cookie,服务器端收到消息后,解析里面的sessionid即可。
跨站请求伪造
第三方站点伪造用户请求,服务端未进行请求的校验,使第三方成功请求;
解决办法:使用token,在请求中加一个随机token,服务端进行token校验.
三次握手和四次分手
GET和POST的区别
GET没有body,只有url,请求放在url中;POST请求的数据在body中
post没有大小限制,get请求受url长度限制
post的安全性要比get高
当你点击一个网址后,发生了什么?
操作系统
并发和并行的区别?
并发是两个或多个任务在同一时间段发生;
并行是两个或多个任务在同一时刻发生;
在单核CPU中,并发只是宏观上的含义,在微观上,任务是交替执行的,比如一秒钟的时间内,多个任务并发执行,但在CPU调度时,0-30ms在执行A,30-60ms在执行B,所以在宏观上,一秒钟的时间段内,又两个任务并发执行,在微观上,A和B在分时交替执行.
在多核CPU下,并发任务可以被分配在多个CPU上,实现并行执行.
进程和线程
首先,进程是系统进行资源分配的最小单位,而线程是进行任务调度的最小单位。其次,一个进程可以有多个线程,但最少要有一个线程,这个线程的主要做用是创建其它线程。而且,每个进程在系统中都有唯一的进程控制块PCB,系统可以根据PCB感知进程的存在,并对进程进行调度,每个线程在进程中也有唯一的线程控制块TCB,作用与进程类似,TCB中存放了线程的标识符,状态,优先级等基本信息。最后,在开销方面,创建进程的开销比线程大,进程在创建时,需要操作系统为其分配内存,文件,I/O等资源,而线程是共享所属进程的内存地址和资源的。
线程的三种实现方式?
内核支持线程、用户级线程、混合级线程
有什么区别?
首先,内核线程的创建,撤销以及阻塞都是在内核实现的,而用户线程的操作在用户态实现。其次,内核线程的调度单位是线程,而用户线程的调度单位仍然是进程.这里调度单位的区别主要体现在,如果一个线程在执行系统调用时被阻塞,内核可以调度其它线程占用CPU,而在用户级线程中,不仅该线程会被阻塞,该进程下的所有线程都会被塞。当然,内核线程也有缺点,当切换涉及到用户线程时,需要从用户态转到核心态进行,这是因为内核线程的调度和管理都是在内核实现的,所以开销比较大.
混合级线程是把内核级线程和用户级线程进行了组合,形成了一对一、多对一、多对多三种不同的模型.
其中,一对一是一个用户线程对应一个内核线程,其中一个阻塞时,可以调用另外一个执行,缺点是每个用户线程都需要有内核线程的支持,开销较大。而多对一是多个用户线程映射到一个内核线程,但它的缺点是如果其中一个线程执行了系统调用,这个进程下的所有线程都会被阻塞。最后的多对多则是将多个线程映射到多个或少量的内核线程,内核线程的数目可以根据用户线程进行动态调整。java中使用的是一对一,因为windows和Linux提供的是一对一的模型。
内核态和用户态的区别?
内核态运行的是操作系统程序,而用户态运行的是用户程序。当处于用户态时,进程所访问的内存和资源会收到限制,占用的CPU可以被占用,而在内核态时,进程访问的内存和资源不会受到限制,CPU也不可以被占用。
单核可以处理多线程吗?怎么处理?
可以,单核CPU在同一时间只能有一个线程执行,CPU在多线程之间快速切换,造成了多线程的同时执行.
CPU主频
CPU主频又叫CPU内核工作的时钟频率,即CPU的运算速度,主频越高,一个时钟周期完成的指令数越多.CPU的运算速度也就越快.
进程间的关系?
一个进程可以创建另一个进程,创建进程是父进程,被创建的进程是子进程,子进程可以创建更多的孙进程
子进程可以继承父进程的资源,在子进程被终止后,资源会还给父进程.父进程终止后,子进程也会被终止.
进程的创建?
操作系统以create原语创建进程,主要分为4个步骤,①创建PCB进程控制块②分配资源③初始化PCB④插入到就绪队列等待调度
进程的终止?
①从PCB集合中检索当前进程PCB,获取状态信息
②若正在执行,终止执行,标志为可调度
③若有子孙进程,终止子孙进程
④归还资源给系统或者是父进程
⑤将当前进程的PCB从集合中移除
进程的同步机制?
硬件同步
利用操作系统的指令实现,例如TS(test-and-set),如果临界资源为true,表示正在使用,如果为false,则表示可以进入,并将临界资源设置为true,否则循环等待.
这种方式的缺点是,如果资源正在被使用,等待的资源会处于一种"忙等"的状态,也就是说,当它不能获得资源时,应释放cpu资源.
信号量
①整型信号量
在申请资源时,先判断信号量符不符合预期,如果符合预期,修改信号量并进入就绪状态,如果不符合预期,则会轮询判断.在释放资源时,对信号量进行递增操作
这种方式和硬件同步存在同样的缺点,如果不符合预期,它会一直轮询,同样会使进程陷入忙等的状态.
②记录型信号量
相对于整型信号量,它解决了进程在申请资源时的忙等状态.在申请资源时,它不会一直测试,它的思想是先对信号量进行操作,如果符合预期,则表示申请成功,进入就绪状态,如果不符合预期,则进行自我阻塞.在释放资源时,也是同样的道理,如果符合预期,资源释放成功,如果不符合预期,则会唤醒阻塞的进程,从而避免了进程的忙等.
记录型信号量所存在的问题是,它只适用于多进程并发访问一个资源,如果一个进程需要获得多个资源才执行,就需要适用AND型信号量.
③AND型信号量
对于记录型信号量,它实现了一个进程可以获得多个临界资源的情况.它在执行申请操作时,会一次性将所需的资源全部分配给进程,只要一个资源未分配,其它资源也不会被分配.在执行释放操作时,会一起释放.
④信号量集
对于AND型信号量来说,信号量集在它的基础上做个扩充,将信号量从常量改成了变量,当申请资源的值大于等于所设定的资源最小值,就会进行资源分配,并且从资源总量中减去所分配的资源.
管程
①管程是什么?
管程是把共享资源以及对共享资源所做的操作进行了封装
②管程所解决的问题?
a.在进程同步中,每个进程都要具备wait和singnal两个操作
b.在执行次序不同时,容易引发死锁问题
③与进程的区别?
a.进程可以随生命周期被创建和撤销,而管程则是操作系统中的一部分.
b.进程的作用是实现并发,管程则是为了在并发过程中解决共享资源的使用问题.
c.进程可以主动调用管程,而管程则是被动工作方式,不能主动调用
④管程的操作过程是怎样的?
当进程在调用管程进行同步时,如果满足所定义的条件变量时,则继续执行,如果不满足,进程会把自身挂在条件变量的阻塞队列中,并释放管程,这时管程可以被其它进程所调用.当正在调用管程的进程发现之前的条件满足,则会对阻塞的进程进行唤醒,此时会有两个进程调用管程,目前有一个较好的解决办法就是将唤醒操作放在最后执行,等现有的任务执行完成之后,进行唤醒,此时被唤醒的进程就可以使用管程了.(相当于一个进程携带自己的条件进入管程,条件满足执行,条件不满足,将自己挂在条件队列后)
⑤管程是如何实现进程同步的?
管程保证了同一时刻只有一个进程进入管程,并且这个进程在管程中还处于活跃状态,如果不满足管程中的条件变量,则会主动挂在条件变量后的阻塞队列中,如果满足状态,则可以对共享资源进行特定操作.
⑥管程中的两个队列?
进入管程的等待队列 管程中的条件阻塞队列
死锁
什么是死锁?
一组进程中的每个进程都在等待由这组进程中其它进程引发的事件
死锁的条件是什么?
互斥
进程对所占有的资源进行排他性使用,一段时间内,资源只能被这一个进程使用
请求和保持
进程已经保持了一个资源,但又提出了新的资源请求,而这个资源正在被其它进程所占有.
不可抢占
进程已获得的资源不可被抢占
循环等待
一系列进程互相持有其它进程所需要的资源
如何解决死锁?
破坏以上四个条件中的其中一个
MySQL
锁
锁粒度
表锁
表锁的锁粒度大,加锁快,开销小,但是锁冲突的概率大,并发度较低
行锁
行锁锁定当前数据行,锁的粒度小,加锁慢,发生锁冲突的概率小,并发度高
表锁和行锁的区别?
1. 加锁速度的快慢,表锁加锁比较快,行锁加锁比较慢
2.死锁区别,表锁不会出现死锁的情况(因为会一次性获取所需的全部锁),行锁会出现死锁
兼容性
排他锁/写锁
在进行写操作时,其它的读写操作都会被阻塞
共享锁/读锁
多个读操作可以同时进行,允许读操作,阻塞写操作
锁模式
间隙锁
Gap Locks
加在索引的区间中
Next-Key Locks
是Gap间隙锁和Record记录锁的组合,记录锁加在索引上,间隙锁加在索引之间,实现原理是将当前行与上一条数据和下一条数据的区间进行锁定,保证范围读取的数据是一致的
加锁时机
在数据库的增删改查中,只有增删改才会加排他锁,而查询并不加锁,可以在select语句后显式加共享锁(lock in share mode)或排他锁(for update)
存储引擎
InnoDB
索引
聚簇索引
数据与索引一起存放,找到索引也就找到了数据
如何防止幻读?
innoDB使用MVCC+间隙锁防止幻读的出现
在查询时加for update时,会用next-key解决幻读问题,新的insert和update会阻塞
在查询不加for update时,会用mvcc解决幻读问题,新的insert和update不会阻塞
间隙锁
间隙锁使InnoDB不仅仅锁定涉及的行,还会对索引中的区间进行锁定,以防止幻影行的插入
MVCC多版本并发控制
是行级锁的一个变种,只在可重复读和读已提交两个隔离级别下工作,在很多情况下避免了加锁操作.innoDB的每一行都冗余了两个字段,一个是创建版本,一个是过期版本,事务每次读取数据时,只查找早于当前事务版本的数据,这样可以确保事务读取到的数据,要么是在事务开始前已经存在的,要么是事务自身修改过的
为什么必须要有主键?
首先是为了满足mysql的索引数据结构B+树的特性,必须有主键作为索引,如果没有指定的话,innoDB会从当前表中找出不重复的一列作为索引,如果没有的话,innoDB会在后面添加一行rowID作为主键索引.
MyIsam
使用表锁,不支持事务,非聚簇索引
char和varchar的区别
char(n) 固定长度,申请的长度就是最终的长度,类似于静态数组
varchar(n) 可变长度,类似于可变数组—列表
经常变化的字段用varchar;
知道固定长度的用char;
尽量用 varchar;
超过255字节的只能用varchar或者text;
能用varchar的地方不用text;
超长的,例如存储整个html用text。
索引
索引类型
普通索引
由关键字key或Index下标定义的索引,应用最多的索引
唯一索引
列值必须唯一,但可以为空值
组合索引
使用多条列作为一个索引进行检索,使用最左匹配原则.
全文索引
作用于myIsam的存储引擎,适用于char,varchar,text的列
like在什么情况下会用到索引?
like只要不以%开头都会用到索引,因为%在前的时候使用全表扫描
索引策略
聚簇索引
数据与索引集合存放,叶子节点存储的是数据
非聚簇索引
数据与索引分开存放,叶子节点存储的是指针
数据结构
B树与B+树的区别
B+树是B树的升级版,B+树的叶子节点是一个链表,当进行范围查找的时候,B+树先从根节点出发,在到叶子节点,找到下限之后,根据链表找上限,然后将数据返回.B树的查找则不同,B树从根出发,找到叶子节点,确定范围下限,中序遍历整棵树找范围上限,最后将数据返回.
事务
原子性
事务的原子性操作,对数据的修改要么全部成功,要么全部失败,数据库基于日志的Redo/Undo机制实现原子性
Redo/Undo机制
将所有对数据的更新操作都写入到日志中。Redo用来记录数据被修改后的值,可以用来恢复已事务更新但未写入磁盘的数据,Undo用来记录数据被修改前的值,保证事务失败后能够进行回滚
一致性
持久性
隔离性
读未提交
一个事务可以读取到另一事务未提交的数据
未解决任何问题
读已提交
一个事务需要等另一个事务提交后才能读取数据
解决了脏读
脏读:一个事务访问数据,对数据进行了修改但未提交,而另一个事务读取了这个数据,读取的数据就是脏数据
可重复读
同一个事务多次读取同样记录的结果是一致的
解决了脏读、不可重复读
不可重复读:事务前后两次读取的数据不一致
串行化
最高的事务隔离级别,所有事务串行顺序执行
解决了脏读、幻读、不可重复读
幻读:当某个事务在读取某个范围的数据时,其它事务在该范围插入了新的记录,之前事务再次读取时,会产生幻行.
隔离级别从低到高,隔离效果越来越好,性能却越来越差
为什么?
隔离级别中的加锁是性能消耗的主要原因
读未提交
未加任何锁,也不能实现隔离效果,所以性能是最好的
读已提交
它在实现时为了兼顾数据问题,又需要有一定的并发性,在上锁机制上会比串行化优化很多,所以性能也会比较好
可重复读
与读已提交相同
串行化
在读的时候加的是共享锁,不能写入,写的时候加的排他锁,不能写入和读取.如果其它事务不能长时间写入会报超时,所以它的性能也是最差的,对于它来说也没什么并发性
优化
建表的时候设置列默认值
1.设置默认值可以避免程序中的非空判断 2.在myIsam下,null对索引和磁盘都会有额外的开销,允许为null的列会比not bull多1bit
查询较多的字段设置索引
对长sql设置慢查询
慢查询默认不开启,默认时间是10秒
kafka
nginx
是什么?
nginx是Web服务器,使用异步非阻塞模型,主要处理客户端和服务器的请求分发.
什么是异步非阻塞模型?
异步针对于被调用者而言,当调用发出后,被调用者当即返回,任务处理完成后,通过回调或其它方式返回结果.而非阻塞针对于调用者而言,当调用发出后,调用者去处理其它任务,不会阻塞当前线程
解决了什么问题?
首先是为了能在高并发场景下分担后端服务器的压力,最大可支撑5w连接
其次,在资源使用方面,大约1w个连接消耗的内存是2.5M
最后,在性能方面,可以做到7*24小时不间断运行
怎么用?
反向代理和负载均衡
常用的配置是
listen:反向代理访问的端口
server_name:服务器ip
proxy_pass:tomcat的地址和端口
redis
是什么?
工作在内存上的nosql数据库
解决了什么问题?
高并发场景下分担了数据库的压力
减少了磁盘的IO
支持事务,数据结构简单
数据持久化
RDB快照
只有一个dump文件,方便持久化
性能较好,当写数据时,子进程完成写操作,主进程不受影响
缺点是数据安全性低,RDB隔一段时间进行持久化,如果持久化之间redis故障,会发生数据丢失
AOF日志
数据安全,采用追加的方式记录
缺点是数据文件较大,恢复速度比RDB慢
分布式锁
加锁通过set指令来设置值,成功则返回,否则循环等待,在timeout获取时间内仍未获取到锁,就获取失败
使用:先用setnx争抢锁,抢到之后,用expire给锁加一个过期时间防止锁忘记释放
redis list队列和kafka的区别:
redis的发布订阅并不支持分组,kafka支持分组且同一个组里只有一个会收到消息,其它起到负载均衡的作用
kafka相对于来说较稳定,数据量大选用redis可能被击穿
kafka实现了生产方和消费方业务的解耦
击穿,雪崩,穿透
击穿
是什么?
高并发请求一个恰好失效的key
怎么解决?
将热点key的过期时间调整为永不过期
雪崩
是什么?
大量key在同一时间失效
怎么解决?
不同的key设置不同的过期时间
穿透
是什么?
查询一个一定不存在的key
怎么解决?
将空结果进行缓存,过期时间不超过5分钟
数据结构
实现set,采用的数据结构
hashSet
使用hashMap实现,使用了数组和散列算法.(查找快但不能排序)
treeSet
treeSet使用treeMap实现,底层使用红黑树.(可以排序,但查找慢)
实现队列,采用的数据结构
线性表
先进先出,优势是随机访问的效率高,因为可以进行索引,缺点是扩容复杂
数组
实现唯一的缺点是建立时要确定空间大小。
链表
链表实现指针域要占用空间,频繁的new和delete会消耗时间开销
二叉查找树
特点
左子节点比父亲节点小,右子节点比父亲节点大,正常情况下的查找时间复杂度为O(logn)
缺点
极端情况下,会近似退化为一条链表,查找的时间复杂度就会变成O(n)
AVL平衡二叉树
特点
1.具有二叉查找树的全部特性
2.每个节点的左子树和右子树的高度差至多等于1
作用
为了解决二叉查找树在极端情况下退化为一条链表的情况
规则
左-左型:右旋
右-右型:左旋
左-右型:先左旋后右旋
右-左旋:先右旋后左旋
红黑树
特点
1.具有二叉查找树的全部特点
2.根节点为黑色
3.每个叶子节点都是黑色的空节点,叶子节点不存数据
4.任何相邻的节点都不能同时为红色,红色节点被黑色节点隔离开
5.每个节点到其可达的叶子节点,所包含的黑色节点,数目相同
作用
为了解决平衡二叉树在插入时进行频繁左旋右旋的问题
B树和hash索引的区别?
1.查找方式不同,hash索引的查找与hashMap的查找类似,先计算键的hashCode,然后根据hashCode寻找对应的记录指针,与指针所指向的数据做进一步比较.最后返回数据.B树的查找则是根据左小右大的原则进行查找.
2.等值与范围hash索引支持等值查询,不支持范围查询(因为原先是有序的数据,hash算法之后有可能成了无序的数据)
3.hash索引也不支持最左匹配原则,如果存在大量重复键的话,还可能会出现哈希碰撞的问题(哈希碰撞:不同的输入得到同一个hash值.防止哈希碰撞的方法:扩大哈希值的取值空间)
排序
快速排序
相当于分治版的冒泡,它的思想是将所有数与取得基准值进行比较,比它小的放在左边,大的放在右边,再对左右两边递归如上操作
算法
单例模式
判断链表是否有环?
用两个栈实现一个队列?
其它
高并发的实现优化
首先,尽量将请求拦截在系统上游,例如使请求数量尽量少且请求路径尽量短,js层面可以将抢购按钮在用户点击之后
置灰,禁止重复提交,在x秒内只能提交一次,其次,针对这种读多写少的情况,应多使用缓存进行,例如在秒杀前将数据同
步至redis中并进行库存预热,在活动结束后异步修改数据库.最后,如果流量非常大的话,需要进行限流,服务降级,熔断
以及隔离等措施来保证系统正常运行.首先,尽量将请求拦截在系统上游,例如使请求数量尽量少且请求路径尽量短,js
层面可以将抢购按钮在用户点击之后置灰,禁止重复提交,在x秒内只能提交一次,其次,针对这种读多写少的情况,应多
使用缓存进行,例如在秒杀前将数据同步至redis中并进行库存预热,在活动结束后异步修改数据库.最后,如果流量非
常大的话,需要进行限流,服务降级,熔断以及隔离等措施来保证系统正常运行.
点击一个网址后发生了什么?
1.浏览器会先检查本地缓存,如果有缓存,浏览器解码响应
2.请求DNS服务器获取CDN的IP
3.浏览器根据IP地址对服务器建立三次握手
4.浏览器通过TCP连接发送HTTP请求
5.浏览器收到HTTP响应,并可能关闭连接,或重新用于其它请求
6.浏览器检查响应是重定向还是条件响应(3xx或4xx等)
7.如果可缓存,则进行缓存
8.浏览器解析html,运行js脚本,进行css渲染
9.浏览器呈现响应

收藏

收藏
0 条评论
下一页