2020.11.25
概要
本次面试是最近刚面的。
PS:本人java开发2年经验,这次面的是滴滴出行(小桔科技)java开发岗。
2020.11.30
滴滴又打来电话了, 预定12.3.星期四面试,不过是另一个java岗位,还是一面;
说明这次的凉了,然后又被HR捞起来了……
很迷,我都不知道该说什么好……
等面完后再总结一篇新的文章吧……
过程
1.2020年11月16日,本人投给滴滴的简历变成了"被查看"状态(也是在拉勾APP投的),不过多会投的就忘了,应该也是前一二天吧。
PS:投给字节跳动的简历还是"投递成功"状态,甚至没有被查看,看来字节跳动是真的不在拉勾上更新简历进度,不过个人觉得这个也不是很重要,只是记录一下。
2.2020年11月23日,本人接到了滴滴的电话,预约面试时间,本人预约了11月24日20:30的面试。
3.然后收到了邮件,其中写着面试时间,以及面试方式;这次要使用腾讯会议PC版视频面试。
4.2020年11月24日,18:00,HR又打来电话,询问面试时间是否有调整,很周到;本人回复不用调整。
5.2020年11月24日,20:30-21:15,进行了滴滴视频面试。
6.今天25日,等待结果中,希望无论如何给一个结果通知……
面试内容
1.自我介绍。
*期间面试官自言自语说本人工作时间不长,本人目前2年java开发经验,如果还不够的话,难道是必须要3-5年?
2.询问做过的项目,主要问项目问的比较多,以及项目细节。
3.你的项目业务比较复杂吗?
答:是的。然后介绍了一个大批量推送的需求是如何实现的。
4.接第三问:你刚才介绍的是技术实现复杂,不是业务复杂。
答:又扩展介绍了一下项目流程,不过面试官认为又回归到介绍技术实现复杂了;只好回答,那它可能并没有那么复杂。
5.编写一个程序,有三个线程,分别输出A/B/C,现在让它们按顺序输出ABC,并循环十次,你能想到几种实现思路?
答:使用公平锁Reentrantlock(true)实现;有一个java线程池也可以实现;使用线程的wait()与notify()也可以实现。(synchronized个人感觉不能实现,这个是非公平锁,不讲顺序)
6.编码实现第五题(点击腾讯会议的共享屏幕),参考答案如下(终于碰到一个可以做出来的编程题了):
首先创建一个自定义线程类,准备使用Reentrantlock:
import java.util.concurrent.locks.ReentrantLock; public class MyThread extends Thread{ private ReentrantLock lock; private String str; public MyThread(String str, ReentrantLock lock){ this.str = str; this.lock = lock; } @Override public void run() { for(int i = 0; i<10; i++){ lock.lock(); System.out.println(str); lock.unlock(); } } }
然后是main方法:
import java.util.concurrent.locks.ReentrantLock; public class Test { public static void main(String[] args) { //有三个线程,分别输出ABC,现在要求线程按顺序输出并且循环10次 ReentrantLock lock = new ReentrantLock(true); MyThread t1 = new MyThread("A",lock); MyThread t2 = new MyThread("B",lock); MyThread t3 = new MyThread("C",lock); t1.start(); t2.start(); t3.start(); } }
这样就实现了题目要求。
*之后明显感觉难度开始上升。
7.你知道volatile关键字解析吗?(从来没听过)
百度:
https://blog.51cto.com/12222886/1964228 https://www.cnblogs.com/dolphin0520/p/3920373.html ●Java中的volatile 在Java程序中,如果一个变量被volatile关键字修饰,那么这个变量就具有了有序性和可见性。 有序性:java语言中提供了synchronized和volatile两个关键字保证线程之间操作的有序性,也就是他可以使CPU指令有序。 可见性:当一个线程操作一个被volatile修饰的变量时,这个变量的修改对其他所有线程都是可见的,因为此时的操作不会将该变量读到当前线程的CPU缓存中进行操作,而是直接操作内存 ●个人理解与总结 volatile修饰变量后,这个变量会存入内存,变成共享变量,线程读写时直接操作内存中的这个变量,跳过CPU cache这一步,因此在读取这个变量时总会返回最新写入的值; 并且,在操作这个变量时是有序的,CPU指令有序; 并且,在访问volatile变量时不会执行加锁操作,也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。 ●synchronized原理 synchronized可以修饰方法、对象、类;在修饰方法时又分为实例方法、静态方法、代码块。 对于同步块的实现使用了monitorenter和monitorexit指令:他们隐式的执行了Lock和UnLock操作,用于提供原子性保证。 monitorenter指令插入到同步代码块开始的位置、monitorexit指令插入到同步代码块结束位置,jvm需要保证每个monitorenter都有一个monitorexit对应。 这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。 线程执行到monitorenter指令时,会尝试获取对象所对应的monitor所有权,也就是尝试获取对象的锁;而执行monitorexit,就是释放monitor的所有权。 详情见:https://www.cnblogs.com/wuzhenzhao/p/10250801.html
8.数据库有两个条件分别加了索引,按照两个条件查询时索引怎么走(两个索引同时用吗?不知道)
百度与个人总结(MySql):
(1)首先,索引可以给一个字段加,也可以给多个字段加,如图(Navicat):
其中,名是自己起的,栏位对应表中的字段;
(2)加索引之后,当select对应字段、或group by、或order by、或其它增删改查时,都可能会用到索引,提高sql执行效率。
如果经常用到多个字段(例如group by或order by多个字段),就应该给多个字段加一条索引。
关于order by,只有出现在where条件中,才可能会走索引;group by等也类似。详情见:https://www.cnblogs.com/zhaoyl/archive/2012/05/04/2483513.html
(3)如果两个条件都加了索引,sql执行时,会选择影响行数较少的索引,即区分度大的索引。(使用explain分析结果时,rows的值小的。)详情见:https://blog.csdn.net/qq_22771739/article/details/85853620
9.如何知道一句sql走了哪个索引?(好像是有一个语法是调试sql用的,然而忘了,还是不常用)
百度:参考网址:https://www.cnblogs.com/wqbin/p/12124621.html
使用explain,可以查看sql是否走了索引
explain select * from test group by user
之后,可以查看结果(本人用Navicat执行的sql)。
举个例子:
(1)数据库表
(2)表设计
(3)索引(自己设置的)
(4)执行【explain select * from test group by user】,返回结果:
其中,type为index,说明有索引;(type结果值从好到坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL)
key为实际使用的索引,user_index,说明使用了这个字段的索引。
possible_keys为可能需要使用的索引,正常情况下与key相同;这里为空,说明可能用不到索引也能高效完成查询(不过后来发现还是用索引好)。详细原因可见:https://blog.csdn.net/eden_Liang/article/details/108026148
(5)执行【explain select * from test 】返回结果:
可以看到,type为ALL,并且key为空,说明没有使用索引。
10.线程池实现原理(只基本会用,没研究过原理)
百度:
为什么需要使用线程池?
●当使用大量线程时,减少大量的new线程与GC回收线程的额外开销。
首先,java有四种线程池:
●newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
●newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
●newScheduledThreadPool
创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
●newCachedThreadPoo
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
原理:
所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。
只有当阻塞队列满了后,才会触发非核心线程的创建。所以非核心线程只是临时过来打杂的。直到空闲了,然后自己关闭了。
线程池提供了两个钩子(beforeExecute,afterExecute)给我们,我们继承线程池,在执行任务前后做一些事情。
线程池原理关键技术:锁(lock,cas)、阻塞队列、hashSet(资源池)
详情网址:
https://www.cnblogs.com/rinack/p/9888717.html
https://www.cnblogs.com/franson-2016/p/13291591.html
11.线程interrupt()作用(已经被问蒙了,这个也不会了)
百度:
interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。 *相当于只是改变了isIntrerrupt()返回的boolean值。(当然还有些其它操作,不过主要是改变这个值,其它方法中会根据这个方法判断当前线程状态。) 如果线程阻塞前调用这个方法,那么当该线程遇到阻塞时,会抛异常,停止运行; 如果线程处于阻塞状态,调用这个方法,也能让线程抛异常,停止运行。 更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。 如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException。 详情见:https://blog.csdn.net/liujian8654562/article/details/79875853
再总结下线程基本方法:
●线程有五种状态,新建,就绪,运行,阻塞,死亡 ●new Thread()创建的线程,是新建状态的。 ●使用start()方法,线程进入就绪状态;等待获得资源后,执行run()方法中的内容,此时算运行状态。 ●线程运行状态中,遇到某些情况时会进入阻塞状态,如需要执行输入输出但是资源不足;或者人为使用sleep(),suspend(),wait()方法,也会让线程处于阻塞状态。 ●线程调用stop(),destory(),或者run()方法执行完毕,就会进入死亡状态。 ●sleep()方法可以让线程等待一段时间后再运行,此时是阻塞状态,不释放资源;此时会让出cpu,但是不释放锁。 ●wait()方法可以让线程进入阻塞状态,释放资源;JVM把这个线程放入等待池;需要等待其它线程使用notify()或notifyAll() ●wait(long timeout)方法可以让线程进入阻塞状态,释放资源;需要等待其它线程使用notify()或notifyAll(),或者超过时间后,这个线程变为就绪状态 ●notify()方法可以让线程从阻塞状态变为就绪状态,JVM把这个线程从等待池中取出;一般是其它线程调用这个方法。 ●suspend()使线程阻塞,resume()唤醒线程,这两个方法及其它所有方法在线程阻塞时都不会释放占用的锁(如果占用了的话);而wait()和notify()这一对方法会释放锁。 ●yield()的作用是让步,它能够让当前线程从“运行状态”进入到“就绪状态”,从而让其他等待线程获取执行权,但是不能保证在当前线程调用yield()之后,其他线程就一定能获得执行权,也有可能是当前线程又回到“运行状态”继续运行。 详情见:https://blog.csdn.net/wordwarwordwar/article/details/85924858 ●需要注意,sleep(),suspend(),resume(),yield(),start(),stop(),destory()这些方法的主体是线程,如new Thread().suspend(); ●而wait(),notify(),notifyAll()的主体可以是任何对象,例如new String().wait();具体使用方式见下方网址: 关于notify()与notifyAll():https://blog.csdn.net/qq_42547338/article/details/107448668 ●还需要注意,java中,destory()方法并没有被实现,例如new Thread().destory(),调用该方法只会抛出一个异常:NoSuchMethodError();这个方法需要自己继承Thread类后重写。 ●还需要注意,直接使用stop(),会立即停止线程,可能导致文件流、数据库连接没有关闭,因此不推荐使用。推荐标志位与interrupt()等组合使用来停止线程,详细使用如下(记住只用interrupt()是不会真的停止线程的): https://www.cnblogs.com/liyutian/p/10196044.html
12.你还有什么问题?
后记
这些问题的答案本人会继续完善。
整体感觉,前半部分还可以,后半部分就都不会了,感觉明显从编程题后难度提升了。
好不容易遇到一个会做的编程题,这次不想又不明不白的凉了。
希望有戏吧……