一面
1、聊聊你研究生的课题(智能机器人相关)
2、几个人做的啊,分工是什么样的
3、如果以后想把你们做的东西商业化 产品化,怎么去收集用户的信息?
云?用户定期上传数据到云服务器,公司负责处理这些数据(不懂。朝着云的方向扯了一些)
4、 进程与线程的区别
调度:进程是资源管理分配的基本单位,线程是程序执行调度的基本单位。 切换:线程上下文切换比进程上下文切换要快得多。 拥有资源: 进程是拥有资源的一个独立单位,线程不拥有系统资源,但是可以访问隶属于进程的资源。 系统开销: 创建或撤销进程时,系统都要为之分配或回收系统资源,如内存空间,I/O设备等,OS所付出的开销显著大于在创建或撤销线程时的开销,进程切换的开销也远大于线程切换的开销。
5、线程一般存在什么地方?线程里有什么东西?
额。有没有大佬懂的评论区指导一波,当时卡住了。。
6、说说看自己对LRU的理解。实现思路
最近最少使用机制,是操作系统中用来实现页面替换的一种优秀的算法。本质上是一种双向链表的数据结构,用哈希表来辅助维护里面的节点位置。插入时先看看哈希表里有键值冲突:没有的话,用键值新构造一个节点,插入到链表中,并移到链表头,再看看有没有越界,越了的话要把尾部节点删除;同时放入哈希表中。有的话,用新值代替旧值,然后移到链表头。若读取,判断键对应的节点存不存在,存在的话读值并把节点移到头部。其中涉及到把节点移到链表头、删除链表尾的操作。附上实现,面试官让口述:
public class Solution {
/**
* lru design
* @param operators int整型二维数组 the ops
* @param k int整型 the k
* @return int整型一维数组
*/
class DoubleListNode {
int key;
int val;
DoubleListNode pre;
DoubleListNode next;
DoubleListNode() {}
DoubleListNode(int key, int val) {
this.key = key;
this.val = val;
}
}
DoubleListNode head = new DoubleListNode(-1, -1);
DoubleListNode tail = new DoubleListNode(-1, -1);
HashMap<Integer, DoubleListNode> map = new HashMap<>();
public int[] LRU (int[][] operators, int k) {
// write code here
ArrayList<Integer> ans = new ArrayList<>();
head.next = tail;
tail.pre = head;
int size = 0;
for(int i = 0; i < operators.length; i++){
int tmp_key = operators[i][1];
DoubleListNode node = map.get(tmp_key);
if(operators[i][0] == 1){
if(node == null){
DoubleListNode new_node = new DoubleListNode(tmp_key, operators[i][2]);
map.put(tmp_key, new_node);
add2head(new_node);
size++;
if(size > k){
DoubleListNode weiba = removeTail();
map.remove(weiba.key);
size--;
}
}else{
node.val = operators[i][2];
add2head(node);
}
}else{
// node = map.get(tmp_key);
if(node == null) ans.add(-1);
else{
ans.add(node.val);
move2head(node);
}
}
}
int[] res = new int[ans.size()];
for(int i = 0; i < ans.size(); i++){
res[i] = ans.get(i);
}
return res;
}
void move2head(DoubleListNode node){
removeNode(node);
add2head(node);
}
void removeNode(DoubleListNode node){
node.pre.next = node.next;
node.next.pre = node.pre;
}
void add2head(DoubleListNode node){
node.pre = head;
node.next = head.next;
head.next.pre = node;
head.next = node;
}
DoubleListNode removeTail(){
DoubleListNode weiba = tail.pre;
removeNode(tail.pre);
return weiba;
}
}
7、你是怎么理解线程安全的,为什么会出现线程安全的问题呢
线程安全就是说多线程访问同一段代码,不会产生不确定的结果 。 理解线程安全的两个方面:执行控制和内存可见。 执行控制的目的是控制代码执行(顺序)及是否可以并发执行。 内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。 具体的就是去保证原子性、有序性和可见性。 为什么,我当时回答的是多线程操作共享资源时无法保证原子性、有序性和可见性,面试官让结束后再思考一下,请教大佬们指点。
8、怎么解决,有哪些方法
加锁,volatile,原子类,并发包里的一堆东西,按需所求。
9、同步锁有什么问题啊,jdk对此做了啥改进
java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在用户态与内核态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间、专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值和变量等,以便内核态调用结束后切换回用户态继续工作。 如果线程状态切换是一个高频操作时,会消耗很多CPU处理时间;对于那些需要同步的简单代码块,获取锁、阻塞挂起操作消耗的时间比用户代码执行的时间还要长,这种同步策略显然得不偿失的。 synchronized会导致竞争不到锁的线程进入阻塞状态,所以说它是java语言中一个重量级的同步操纵,被称为重量级锁,为了缓解上述性能问题,引入了轻量锁与偏向锁,默认启用了自旋锁,他们都属于乐观锁。 锁升级:最开始的时候处于一个无锁的状态,加锁时首先是偏向锁,当前获取到锁资源的线程,会优先让它再去获取这个锁,如果他没有获取到这个锁,就会升级到一个轻量级锁,比如用CAS来尝试获取锁,如果没有获取成功就自旋,如果自旋了一定次数后,就会升级到synchronized这个重量级锁,保证了性能。
10、自旋锁会消耗cpu吗,为啥
线程自旋是需要消耗cpu的,说白了就是让cpu在做无用功,如果一直获取不到锁,那线程也不能一直占用cpu自旋做无用功,所以需要设定一个自旋等待的最大时间。
反问:携程主要有哪些业务,对我做个简短的评价
一面小结:感觉这个面试官是秋招第一次面试,一开始就说:你等几分钟,我研究一下你的简历hhh,后面问问题的时候每问一个问题都要等个几十秒,嘴上还咕哝着:我想想问个什么好呢,哈哈哈太可爱了。整体上回答的还可以,问的不深。
二面
1、手写一个堆排序
上来就来这有点难顶,写了二十多分中。。。一开始想用PrioityQueue偷懒的被面试官制止了(hh他笑着说:你住手),要求自己用数组模拟堆来写,真不容易,还好不久前看过(背过)。
class Solution {
public int[] sortArray(int[] nums) {
heapSort(nums);
return nums;
}
public void heapSort(int[] nums) {
int len = nums.length - 1;
buildMaxHeap(nums, len);
for (int i = len; i >= 1; --i) {
swap(nums, i, 0);
len -= 1;
maxHeapify(nums, 0, len);
}
}
public void buildMaxHeap(int[] nums, int len) {//构建堆
for (int i = len / 2; i >= 0; --i) {
maxHeapify(nums, i, len);
}
}
public void maxHeapify(int[] nums, int i, int len) {
for (; (i << 1) + 1 <= len;) {
int lson = (i << 1) + 1;
int rson = (i << 1) + 2;
int large;
if (lson <= len && nums[lson] > nums[i]) {
large = lson;
} else {
large = i;
}
if (rson <= len && nums[rson] > nums[large]) {
large = rson;
}
if (large != i) {
swap(nums, i, large);
i = large;
} else {
break;
}
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
2、问商城项目
简历写了一个看视频跟着做的商城网站,没啥好问的,面试官问了一些开放题:怎么避免库存超卖,redis可以用在什么地方,有什么作用,怎么保证用户信息的安全等等;
需要这个商城网站项目源码的朋友可以关注微信公众号:Java团长,然后发送“商城”即可获取~
3、对threadLocal的理解、底层原理
ThreadLocal是 JDK java.lang 包下的一个类,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,并且不会和其他线程的局部变量冲突,实现了线程间的数据隔离。ThreadLocal的应用场景主要有:
(1)保存线程上下文信息,在需要的地方可以获取 (2)线程间数据隔离 (3)数据库连接;
底层原理:每个线程的内部都维护了一个 ThreadLocalMap,它是一个键值对数据格式,key 是一个弱引用,也就是 ThreadLocal 本身,而 value 是强引用,存的是线程变量的值。也就是说 ThreadLocal 本身并不存储线程的变量值,它只是一个工具,用来维护线程内部的 Map,帮助存和取变量。
4、使用threadLocal会出现什么问题
ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。
5、ThreadLocal的key是哪种引用类型?为啥这么设计?
ThreadLocalMap 中的 key 是弱引用,而 value 是强引用才会导致内存泄露的问题
- 若key 使用强引用:这样会导致一个问题,引用的 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用毫无意义,如果没有手动删除,ThreadLocal 不会被回收,则会导致内存泄漏。
- 若key 使用弱引用:这样的话,引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候会被清除(清理key为null的记录),使用完了ThreadLocal最好在手动的remove一下。
- 比较以上两种情况:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可以多一层保障,弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候被清除,算是最优的解决方案。
6、什么是内存泄漏
内存泄漏是指用户向系统申请分配内存进行使用,可是使用完了以后却没有释放,结果那块内存用户不能访问(也许你把它的地址给弄丢了),而系统也不能再把它分配给需要的程序。
7、Java有哪些引用类型?分别有哪些使用场景
- 强引用,任何时候都不会被;垃圾回收器回收,如果内存不足,宁愿抛出OutOfMemoryError
使用场景:我们平常大部分使用的场景都是使用了强引用,比如new创建对象,反射获得一个对象等。 - 软引用,只有在内存将满的时候才会被垃圾回收器回收,如果还有可用内存,垃圾回收器不会回收。
软引用可以和一个引用队列进行关联,如果这个软引用的对象被垃圾回收,就会将这个软引用加入到关联的队列中去。 可用于高速缓存。 - 弱引用(WeakReference),生命周期更短,只要垃圾回收器运行,就肯定会被回收,不管还有没有可用内存。
使用场景: 弱引用用于生命周期更短的,对内存更敏感的场景中,比如占用内存很大的Map,java api中就提供了WeakHashMap使用,就会是的大Map被及时清理掉。 - 虚引用(PhantomReference),虚引用等于没有引用,任何时候都有可能被垃圾回收。虚引用必须和引用队列联合使用,引用队列的作用和软弱引用一样。
使用场景: 我觉得他的使用场景应该在判断一个对象是否被垃圾回收了,什么时候引用队列有新的引用入队了,就说明他被回收了。
反问:部门业务(火车票、机票),技术栈介绍
二面小结:二面主要考察的还是算法与基础知识,不过问的没那么广了,主要对ThreadLocal相关的技术原理展开,自己答的一般,平时确实没用过这个东西,只能硬着头皮去讨论,不过面试官似乎很有耐心,先等我写了好久的代码,然后还给我解释自己不会的地方,很nice
携程一二面的面试题难度跟字节跳动的比起来感觉还是有很大差距的!!!
10月份字节跳动Java面经汇总(信息量有点大):https://docs.qq.com/doc/DTVRQU3BCQ3ZTanZK
HR面
- 自我介绍
- 项目介绍
- 未来对项目的更新迭代有哪些打算
- 跟小伙伴合作的时候出现过分歧吗?怎么解决
- 老家在哪,家庭成员
- 哪位家人对自己影响最大,为什么
- 为什么想来上海,携程对你吸引最大的点在哪里
- 手里有其他公司的offer吗
- 你对之前的两位面试官印象如何,简单评价一下(我来评价面试官?)
- 想不想知道他们对你的评价?(hh好可爱的hr)
- 反问:未来部门分配,导师制度等
总结:携程整个流程非常顺利,基本上笔试以及每一轮的面试之间都是个了一周左右。面试官看起来都像是35左右的大哥,对候选人很友好,至少态度还是很礼貌的,能感觉到对方还是有一些期待的!问的问题还算常规,一面广度,二面深度,HR面综合。(HR的声音太好听了,我说什么她都回答:好呀)
面试中遇到的问题(没回答好的问题)
- 关于线程内部有哪些东西,线程的本质以及它和jvm之间的关系
- redis在交易类系统中一般会有哪些应用,当时只回答了把热点数据存redis,应该还有很多其他的应用
最后,点个赞吧,别光收藏,哈哈哈~
|