今日面经
链接:https://www.nowcoder.com/discuss/614626?source_id=profile_create_nctrack&channel=-1
操作系统中线程、进程联系和区别,上下文切换时发生了什么
1、首先,需要搞明白什么是上下文切换?(面试题)
上下文切换就是从当前执行任务切换到另一个任务执行的过程。但是,为了确保下次能从正确的位置继续执行,在切换之前,会保存上一个任务的状态。
2、 然后,需要明白进程与线程的区别?(网上很多,这里简单说明)
1).线程是进程的一部分。 进程是表示资源分配的基本单位,又是调度运行的基本单位,是程序执行的一个实例; 线程是进程中执行运算的最小单位,即执行处理机调度的基本单位,是进程中的一个执行流。
2).内存空间不同。 每一个进程拥有自己独立的内存空间,而线程共享进程的内存空间。
进程上下文切换与线程上下文切换最主要的区别就是线程的切换虚拟空间内存是相同的(因为都是属于自己的进程),但是,进程切换的虚拟空间内存则是不同的。
同时,这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
3、线程上下文切换比进程上下文切换快的多。
补充:多线程是如何实现的?
主要是CPU通过给每个线程分配CPU时间片来实现多线程的。即使是单核处理器(CPU)也可以执行多线程处理。
时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。
4、在进行上下文切换时,CPU通过时间片分配算法来循环执行任务,Java中的时间片分配算法有哪些?
最简单最常用的就是基于时间片轮转调度算法。 时间片轮转调度算法是非常公平的处理机分配方式,可以使就绪队列的每个进程每次仅运行一个时间片。
原理:在时间片轮转调度算法中,系统根据先来先服务的原则,将所有的就绪进程排成一个就绪队列,并且每隔一段时间产生一次中断,激活系统中的进程调度程序,完成一次处理机调度,把处理机分配给就绪队列队首进程,让其执行指令。当时间片结束或进程执行结束,系统再次将CPU分配给队首进程。
上下文的切换流程如下
(1)挂起一个进程,将这个进程在CPU中的状态(上下文信息)存储于内存的PCB中。
(2)在PCB中检索下一个进程的上下文并将其在CPU的寄存器中恢复。
(3)跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行)并恢复该进程。
时间片轮转方式使多个任务在同一CPU上的执行有了可能。
协程说一下
概念
协程,又称微线程,纤程,英文名Coroutine。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
进程 拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
线程 拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度。
协程 和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
协程和线程区别
那和多线程比,协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,
而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的
性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,
在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
事务四大特性
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
select语句的执行顺序
1 | select |
- 首先执行 FROM 子句, 从学生成绩表中组装数据源的数据。
- 执行 WHERE 子句, 筛选学生成绩表中所有学生的数学成绩不为 NULL 的数据 。
- 执行 GROUP BY 子句, 把学生成绩表按 “班级“ 字段进行分组。
- 计算 avg 聚合函数, 按找每个班级分组求出 数学平均成绩。
- 执行 HAVING 子句, 筛选出班级 数学平均成绩大于 75 分的。
- 执行SELECT语句,返回数据,但别着急,还需要执行后面几个步骤。
- 执行 ORDER BY 子句, 把最后的结果按 “数学平均成绩” 进行排序。
- 执行LIMIT ,限制仅返回3条数据。结合ORDER BY 子句,即返回所有班级中数学平均成绩的前三的班级及其数学平均成绩。
Delete * from table where id>2 执行这条语句过程中发生了什么?(胡说一通)

数据库的索引
一、数据索引是干什么用的呢?
数据库索引其实就是为了使查询数据效率快。
二、数据库索引有哪些呢?
- 聚集索引(主键索引):在数据库里面,所有行数都会按照主键索引进行排序。
- 非聚集索引:就是给普通字段加上索引。
- 联合索引:就是好几个字段组成的索引,称为联合索引。
1 | key ``'idx_age_name_sex'` `(``'age'``,``'name'``,``'sex'``) |
联合索引遵从最左前缀原则
脏读
脏读: ==在不同的事务下,当前事务可以读到另外事务未提交的数据。==另外我们需要注意的是默认的MySQL隔离级别是REPEATABLE READ是不会发生脏读的,脏读发生的条件是需要事务的隔离级别为READ UNCOMMITTED,所以如果出现脏读,可能就是这种隔离级别导致的。不可重复读(修改)
不可重复读: 是指在一个事务内多次读取同一集合的数据,但是多次读到的数据是不一样的,这就违反了数据库事务的一致性的原则。但是,这跟脏读还是有区别的,脏读的数据是没有提交的,但是不可重复读的数据是已经提交的数据。
- 丢失更新(幻读)新增或者删除
事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查 询中出现的数据(并不要求两次查询的 SQL 语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造 成的
幻读,可以锁表。解决
串行化
数据库三大范式
1、1NF:字段不可分,每个字段是原子级别的,上节中看到第一个字段为ID,它就是ID不能在分成两个字段了,不能说我要把这个人的ID、名称、班级号都塞在一个字段里面,这个是不合适的,对以后的应用造成很大影响;
2、2NF:有主键,非主键字段依赖主键,ID字段就是主键,它能表示这一条数据是唯一的,有的读者朋友记性很好,“unique”表示唯一的、不允许重复的,确实它经常会修饰某个字段,保证该字段唯一性,然后再设置该字段为主键;
不存在部分依赖(消除非主属性与码的部分依赖)
3、3NF:非主键字段不能相互依赖。
(消除非主属性与码的传递依赖)
线程池原理
线程池的原理:
其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。
HashMap为什么线程不安全
一直以来都知道HashMap是线程不安全的,但是到底为什么线程不安全,在多线程操作情况下什么时候线程不安全?
让我们先来了解一下HashMap的底层存储结构,HashMap底层是一个Entry数组,一旦发生Hash冲突的的时候,HashMap采用拉链法解决碰撞冲突,Entry内部的变量:
1 | final Object key; |
通过Entry内部的next变量可以知道使用的是链表,这时候我们可以知道,如果多个线程,在某一时刻同时操作HashMap并执行put操作,而有大于两个key的hash值相同,如图中a1、a2,这个时候需要解决碰撞冲突,而解决冲突的办法上面已经说过,对于链表的结构在这里不再赘述,暂且不讨论是从链表头部插入还是从尾部初入,这个时候两个线程如果恰好都取到了对应位置的头结点e1,而最终的结果可想而知,a1、a2两个数据中势必会有一个会丢失.
put方法不是同步的,同时调用了addEntry方法:
addEntry方法依然不是同步的,所以导致了线程不安全出现伤处问题,其他类似操作不再说明,源码一看便知,下面主要说一下另一个非常重要的知识点,同样也是HashMap非线程安全的原因,我们知道在HashMap存在扩容的情况,对应的方法为HashMap中的resize方法:
可以看到扩容方法也不是同步的,通过代码我们知道在扩容过程中,会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组。
当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。
今日面经2
链接:https://www.nowcoder.com/discuss/636764?source_id=profile_create_nctrack&channel=-1
hashmap的原理
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
线程池的参数
一、corePoolSize 线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
二、maximumPoolSize 线程池最大线程数量
一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
三、keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
四、unit 空闲线程存活时间单位
keepAliveTime的计量单位
五、workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
①ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。
②LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。
④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
六、threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
七、handler 拒绝策略
①CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
②AbortPolicy
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
UDP与TDP区别
主要是UDP没有连接状态、速度快,但数据容易丢失,TCP有连接状态、速度慢,但数据较安全,一般根据你对程序数据的安全性要求和传输速度权衡。
Cookie和Session
- session 在服务器端,cookie 在客户端(浏览器)
- session 默认被存在在服务器的一个文件里(不是内存)
- session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)
- session 可以放在 文件、数据库、或内存中都可以。
- 用户验证这种场合一般会用 session
因此,维持一个会话的核心就是客户端的唯一标识,即 session id
索引的类别
1 | 唯一索引: UNIQUE 例如:create unique index stusno on student(sno); |
索引的实现方式(原理)
1 | B+树 |
了解MVCC吗?
链接:https://blog.csdn.net/kyle_wu_/article/details/113287187
- 什么是MVCC
MVCC(Multi Version Concurrency Control的简称),代表多版本并发控制。
MVCC最大的优势:读不加锁,读写不冲突。读写不冲突是非常重要的,极大的增加了系统的并发性能。MVCC机制也是乐观锁的一种体现。
InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的,分别保存这条行的创建时间和行的删除时间。这里的时间并不是实际的时间值,而是系统版本号(事务的ID)。事务开始时刻的系统版本号会作为事务的ID,每次执行一个新事务,系统版本号就会自动递增。随之引出了当前读和快照读。
- MVCC解决了什么问题
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题。
- 什么是当前读和快照读
- 当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读。
当前读就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
- 快照读
快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;
之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
不加锁的简单的 SELECT 都属于快照读,例如:
1 | SELECT * FROM t WHERE id=1 |
与 快照读 相对应的则是 当前读,当前读就是读取最新数据,而不是历史版本的数据。加锁的 SELECT 就属于当前读,例如:
1 | SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE; |
写个SQL行列转换
链接:https://www.cnblogs.com/weibanggang/p/9679301.html
- 使用case when
1 | select name as 姓名, |
- 使用with rollup
1 | select |
自动装箱和拆箱、引用传递
装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递:是指在调用函数时将实际参数的地址直接传递到函数中(的形参),那么在函数中对参数所进行的修改,将影响到实际参数。
Java 中的基本类型,属于值传递。
Java 中的引用类型,属于引用传递。
Java 中的 String 及包装类,属于特殊群体,作为形参时,由于每次赋值都相当于重新创建了对象,因此看起来像值传递,但是其特性已经破坏了,值传递、引用传递的定义。因此他们属于引用传递的定义,却表现为值传递。
垃圾回收器
垃圾收集器的分代
年轻代:Serial, ParNew, Parallel Scavenge
老年代:CMS, Serial Old, Parallel Old
通杀:G1
- Serial收集器
垃圾收集器最基本,发展历史最悠久的收集器。jdk1.3之前是HotSpot新生代收集器唯一的选择。
- 特点
采用复制算法
单线程收集
stop the world 进行垃圾收集时, 必须暂停所有工作线程, 直到完成。
- Serial Old收集器
serial Old是Serial收集器的老年代版本
- 特点:
针对老年代的收集器
采用的是标记整理算法, (还有压缩, mark-sweep-compact)
单线程收集
- ParNew收集器
- 特点:
除了多线程外, 其余的行为, 特点和Serial收集器一样
和Serial收集器公用了不少的代码
- 为什么只有ParNew能与CMS收集器配合
因为Parallel Scavenge(以及G1) 都没有使用传统的GC收集器代码框架, 而是另外独立实现;而其余几种收集器则公用了部分的框架代码。
- Parallel Scavenge收集器
因为与吞吐量关系密切, 也称为吞吐量收集器
- 特点:
a. 和ParNew收集器相似
新生代收集器
采用复制算法
多线程收集
- Parallel Old收集器
Parallel Old垃圾收集器是Parallel Scavenge收集器的老年代版本;
JDK1.6中才开始提供
- 特点:
针对老年代
采用标记整理算法
多线程收集;
- CMS收集器
并发标记清理收集器也称为并发低停顿收集器或低延迟垃圾收集器
- 特点:
针对老年代
基于标记清除算法
以获取最短回收停顿时间为目标;
并发收集, 低停顿
需要更多的内存, 是HotSpot在JDK1.5推出的第一款真正意义上的并发收集器
第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
volatile在单例模式中的作用
链接:https://blog.csdn.net/llllllkkkkkooooo/article/details/115360630
singleton 用了 volatile 修饰,而我们都知道 volatile 的作用
- 不能保证原子性
- 保证共享变量的可见性
- 禁止指令重排序
在单例模式中,volatile的作用主要是 禁止指令重排序
singleton = new Singleton(); 可分解为以下步骤:
- 分配对象内存空间
- 初始化对象
- 设置 singleton 指向分配的内存地址
但是步骤2,3是可能交换的,也就是发生重排序,但是根据 Java语言规范 intra-thread semantics,它是允许那些在单线程内,不会改变单线程程序执行结果的重排序,也就是说,虽然步骤2,3发生重排序,但是对于单线程来说,初次访问对象时,其结果都是正确的,所以即使重排序也无所谓了。
但是在多线程环境中,这就会出现问题,在发生重排序的情况下,会导致线程B在 t3 时间下,判断出 singleton 不为null,那么线程B就会拿到这个 singleton 去做别的事,那么此时这个 singleton 没有初始化,那么就会报错,这就出了问题。
java序列化
链接:https://www.cnblogs.com/wxgblogs/p/5849951.html
如果你只知道实现 Serializable 接口的对象,可以序列化为本地文件。
- 序列化 ID 问题
情境:两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。
问题:C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。
解决:虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清单 1 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。
序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。
- 静态变量序列化
序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量\。
- 父类的序列化与 Transient 关键字
情境:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。
解决:要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就需要有默认的无参的构造函数。 在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取 父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都 是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
- 对敏感字段加密
情境:服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
解决: 在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
- 序列化存储规则
Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。
1-9填九宫格,行列对角线和都相等
二四为肩,六八为足
左三右七,戴九履一
4 9 2
3 5 7
8 1 6
给定一句话,只考虑数字和字母(不考虑大小写),判断是否为回文
1 | // 回文串 |
优缺点
优点:愿意钻研技术,执行能力比较强。
缺点:前面提到执行能力比较强,可能相反我的领导能力有欠缺。
任务完不成,怎么办?
遇到任务完不成,第一时间就是分析任务无法完成的原因,任务完不成造成的结果,如果有不良影响应该怎么解决。如果是由于个人原因造成,这可能是最开始工作不熟,能力不够,一定加倍努力,下次不再犯。而如果是客观原因,比如公司决策,大环境影响,竞争对手的下作手段等,那就只有尽可能想办法弥补造成的损失,随时跟踪任务进度,遇到问题及时预警及处理,尽最大的努力完成任务!