斗鱼面经
目录
当时直接是投的简历到斗鱼的邮箱,然后收到斗鱼的面试邀请,没有做笔试
到了斗鱼,先让我填一张信息表,然后给了我一份纸质笔试题,六七道Java的基础知识理解(简答题),比如接口和抽象类的区别。两道算法题。然后一个写sql语句的题目,一个javascript的题目
笔试很简单,不过貌似这个纸质笔试权重并不大,就是个基础测试。
一面
java基础
集合类
set,list,map
1. List接口的实现类
List接口的实现类常用的有ArrayList和LinkedList,这两个实现类在上面的例子中已经出现过了。
ArrayList类实现了可变的数组,可以根据索引位置对集合进行快速的随机访问。LinkedList类采用链表结构保存对象,便于向集合中插入和删除对象。对于线性结构和链式结构不清楚的朋友可以看我之前的博客“数据的存储结构”,链接:http://www.cnblogs.com/adamjwh/p/5829604.html,也可以自行查阅资料。分别通过ArrayList和LinkedList类实例化List集合如下:
List list1 = new ArrayList(); List list2 = new LinkedList();
2. Set接口的实现类
Set接口的实现类常用的有HashSet和TreeSet,实现如下:
Set<String> set1 = new HashSet<String>(); Set<String> set2 = new TreeSet<String>();
3. Map接口的实现类
Map接口的实现类常用的有HashMap和TreeMap,建议使用HashMap(效率相对较高)。
实现如下:
Map map = new HashMap(); Map map = new TreeMap();
多线程
一、多线程是什么?为什么要用多线程?
介绍多线程之前要介绍线程,介绍线程则离不开进程。
首先 进程 :是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元;
线程:就是进程中的一个独立控制单元,线程在控制着进程的执行。一个进程中至少有一个进程。
多线程:一个进程中不只有一个线程。
为什么要用多线程:
①、为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待;
②、进程之间不能共享数据,线程可以;
③、系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小;
④、Java语言内置了多线程功能支持,简化了java多线程编程。
二、线程的生命周期:
- 新建 :从新建一个线程对象到程序start() 这个线程之间的状态,都是新建状态;
- 就绪 :线程对象调用start()方法后,就处于就绪状态,等到JVM里的线程调度器的调度;
- 运行 :就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞及死亡三种状态。
- 等待/阻塞/睡眠 :在一个线程执行了sleep(睡眠)、suspend(挂起)等方法后会失去所占有的资源,从而进入阻塞状态,在睡眠结束后可重新进入就绪状态。
- 终止 :run()方法完成后或发生其他终止条件时就会切换到终止状态。
三、创建线程的方法:具体实现代码详解请看点击后方链接:多线程扩展一、创建线程的三种方法详细对比(http://www.cnblogs.com/yjboke/p/8919090.html)
1、继承Thread类:
步骤:①、定义类继承Thread;
②、复写Thread类中的run方法;
目的:将自定义代码存储在run方法,让线程运行
③、调用线程的start方法:
该方法有两步:启动线程,调用run方法。
2、实现Runnable接口: 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参方法。
实现步骤: ①、定义类实现Runnable接口
②、覆盖Runnable接口中的run方法
将线程要运行的代码放在该run方法中。
③、通过Thread类建立线程对象。
④、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程执行指定对象的run方法就要先明确run方法所属对象
⑤、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
3、通过Callable和Future创建线程:
实现步骤:①、创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值。
②、创建Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
③、使用FutureTask对象作为Thread对象启动新线程。
④、调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
四、继承Thread类和实现Runnable接口、实现Callable接口的区别。
继承Thread:线程代码存放在Thread子类run方法中。
优势:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。
劣势:已经继承了Thread类,无法再继承其他类。
实现Runnable:线程代码存放在接口的子类的run方法中。
优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
实现Callable:
优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法
建议使用实现接口的方式创建多线程。
五、线程状态管理
1、线程睡眠---sleep:
线程睡眠的原因:线程执行的太快,或需要强制执行到下一个线程。
线程睡眠的方法(两个):sleep(long millis)在指定的毫秒数内让正在执行的线程休眠。
sleep(long millis,int nanos)在指定的毫秒数加指定的纳秒数内让正在执行的线程休眠。
线程睡眠的代码演示:
public class SynTest { public static void main(String[] args) { new Thread(new CountDown(),"倒计时").start(); } } class CountDown implements Runnable{ int time = 10; public void run() { while (true) { if(time>=0){ System.out.println(Thread.currentThread().getName() + ":" + time--); try { Thread.sleep(1000); //睡眠时间为1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } } }
每隔一秒则会打印一次,打印结果为:
倒计时:10 倒计时:9 倒计时:8 倒计时:7 倒计时:6 倒计时:5 倒计时:4 倒计时:3 倒计时:2 倒计时:1 倒计时:0
扩展:Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。
2、线程让步---yield:
该方法和sleep方法类似,也是Thread类提供的一个静态方法,可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。相当于只是将当前线程暂停一下,然后重新进入就绪的线程池中,让线程调度器重新调度一次。也会出现某个线程调用yield方法后暂停,但之后调度器又将其调度出来重新进入到运行状态。
public class SynTest { public static void main(String[] args) { yieldDemo ms = new yieldDemo(); Thread t1 = new Thread(ms,"张三吃完还剩"); Thread t2 = new Thread(ms,"李四吃完还剩"); Thread t3 = new Thread(ms,"王五吃完还剩"); t1.start(); t2.start(); t3.start(); } } class yieldDemo implements Runnable{ int count = 20; public void run() { while (true) { if(count>0){ System.out.println(Thread.currentThread().getName() + count-- + "个瓜"); if(count % 2 == 0){ Thread.yield(); //线程让步 } } } } }
sleep和yield的区别:
①、sleep方法声明抛出InterruptedException,调用该方法需要捕获该异常。yield没有声明异常,也无需捕获。
②、sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态。
3、线程合并---join:
当B线程执行到了A线程的.join()方法时,B线程就会等待,等A线程都执行完毕,B线程才会执行。
join可以用来临时加入线程执行。
以下为代码演示:
public static void main(String[] args) throws InterruptedException { yieldDemo ms = new yieldDemo(); Thread t1 = new Thread(ms,"张三吃完还剩"); Thread t2 = new Thread(ms,"李四吃完还剩"); Thread t3 = new Thread(ms,"王五吃完还剩"); t1.start(); t1.join(); t2.start(); t3.start(); System.out.println( "主线程"); }
4、停止线程:
原stop方法因有缺陷已经停用了,那么现在改如何停止线程?现在分享一种,就是让run方法结束。
开启多线程运行,运行的代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。
具体代码如下:
1 public class StopThread { 2 3 public static void main(String[] args) { 4 int num = 0; 5 StopTh st = new StopTh(); 6 Thread t1 = new Thread(st); 7 Thread t2 = new Thread(st); 8 t1.start(); 9 t2.start(); 10 //设置主线程执行50次,执行结束之后停止线程 11 while (true) { 12 if(num++ == 50){ 13 st.flagChange(); 14 break; 15 } 16 System.out.println(Thread.currentThread().getName() + "..." + num); 17 } 18 } 19 } 20 21 class StopTh implements Runnable{ 22 23 private boolean flag = true; 24 public void run() { 25 while(flag){ 26 System.out.println(Thread.currentThread().getName() + "stop run" ); 27 } 28 } 29 public void flagChange(){ 30 flag = false; 31 } 32 }
特殊情况:当线程处于了冻结状态,就不会读取到标记,也就不会结束。当没有指定方法让冻结的线程回复到运行状态时,我们需要对冻结状态进行清除,也就是强制让线程恢复到运行状态中来,这样可就可以操作标记让线程结束。
Thread类提供该方法: interrupt();(如果线程在调用Object类的wait()、wait(long)、wait(long,int)方法,或者该类的join()、join(long)、join(long、int)、sleep(long)或sleep(long、int)方法过程中受阻,则其中断状态将被清除,还将收到一个InterruptedException。)
5、设置优先级:
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
Thread类中提供了优先级的三个常量,代码如下:
MAX_PRIORITY =10 MIN_PRIORITY =1 NORM_PRIORITY =5 ------------------------------------------------------ ThreadDemo td = new ThreadDemo(); Thread t1 = new Thread(td,"张三"); t1.priority(9); //设置优先级 t1.start(); //设置完毕
六、线程同步与锁。
为什么要进行线程同步?
java允许多线程并发控制,当多个线程同时操作一个可共享资源变量时(如对其进行增删改查操作),会导致数据不准确,而且相互之间产生冲突。所以加入同步锁以避免该线程在没有完成操作前被其他线程调用,从而保证该变量的唯一性和准确性。
不同步会发生的问题?
在介绍同步方法之前先演示一下当多个线程操作一个共享资源时可能会发生的错误,这里用的方法是让线程在执行时睡眠10毫秒,会导致多个线程去操作同一个资源变量:
1 public class SynTest { 2 public static void main(String[] args) { 3 //定义三个线程, 4 MySyn ms = new MySyn(); 5 Thread t1 = new Thread(ms,"线程1输出:"); 6 Thread t2 = new Thread(ms,"线程2输出:"); 7 Thread t3 = new Thread(ms,"线程3输出:"); 8 t1.start(); 9 t2.start(); 10 t3.start(); 11 } 12 } 13 14 class MySyn implements Runnable{ 15 16 int tick = 10; //共执行10次线程 17 public void run() { 18 while(true){ 19 if(tick>0){ 20 try { 21 Thread.sleep(10); //执行中让线程睡眠10毫秒, 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 System.out.println(Thread.currentThread().getName() + " " + tick--); 26 } 27 } 28 } 29 }
输出结果用以下图片展示,可以看到我勾选的部分都发生了冲突数据:
同步方法1:
同步函数:就是用synchronize关键字修饰的方法。因为每个java对象都有一个内置锁,当用synchronize关键字修饰方法时内置锁会保护整个方法,而在调用该方法之前,要先获得内置锁,否则就会处于阻塞状态。
代码演示:请将上方代码的第17行改为以下代码↓
public synchronized void run() {
同步方法2:
同步代码块:就是拥有synchronize关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
代码演示:将上方代码的run方法改成下方代码
public void run() { while(true){ synchronized (this) { //同步代码块 if(tick>0){ try { Thread.sleep(10); //执行中让线程睡眠10毫秒, } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " " + tick--); } } } }
加同步之后的输出数据为:
线程1输出: 10 线程2输出: 9 线程2输出: 8 线程2输出: 7 线程2输出: 6 线程2输出: 5 线程2输出: 4 线程3输出: 3 线程3输出: 2 线程3输出: 1
追加问题:如果同步函数被静态修饰之后,使用的锁是什么?静态方法中不能定义this!
静态内存是:内存中没有本类对象,但是一定有该类对应的字节码文件对象。 类名.class 该对象类型是Class。
所以静态的同步方法使用的锁是该方法所在类的字节码文件对象。 类名.class。代码如下:
public static mySyn(String name){ synchronized (Xxx.class) { Xxx.name = name; } }
--------------------------------以下是总结-----------------------------------
同步的前提:
1、必须要有两个或者两个以上的线程。
2、必须是多个线程使用同一个锁。
3、必须保证同步中只能有一个线程在运行。
4、只能同步方法,不能同步变量和类。
5、不必同步类中所有方法,类可以拥有同步和非同步的方法。
6、如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
7、线程睡眠时,它所持的任何锁都不会释放。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断,消耗资源,降低效率。
如何找问题?
1、明确哪些代码是多线程运行代码。
2、明确共享数据。
3、明确多线程运行代码中哪些语句是操作共享数据的。
七、死锁
进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以它们就互相等待对方占有的资源释放,所以也就产生了一个循环等待死锁。
1 public class DeadLock { 2 3 public static void main(String[] args) { 4 Thread t1 = new Thread(new DeadLockTest(true)); 5 Thread t2 = new Thread(new DeadLockTest(false)); 6 t1.start(); 7 t2.start(); 8 } 9 } 10 11 class DeadLockTest implements Runnable{ 12 13 private boolean flag; 14 static Object obj1 = new Object(); 15 static Object obj2 = new Object(); 16 public DeadLockTest(boolean flag) { 17 this.flag = flag; 18 } 19 public void run(){ 20 if(flag){ 21 synchronized(obj1){ 22 System.out.println("if lock1"); 23 synchronized (obj2) { 24 System.out.println("if lock2"); 25 } 26 } 27 }else{ 28 synchronized (obj2) { 29 System.out.println("else lock2"); 30 synchronized (obj1) { 31 System.out.println("else lock1"); 32 } 33 } 34 } 35 } 36 }
死锁形成的必要条件总结(都满足之后就会产生):
①、互斥条件:资源不能被共享,只能被同一个进程使用;
②、请求与保持条件:已经得到资源的进程可以申请新的资源;
③、非剥夺条件:已经分配的资源不能从相应的进程中强制剥夺;
④、循环等待条件:系统中若干进程形成环路,该环路中每个进程都在等待相邻进程占用的资源。
八、线程间的通信,等待唤醒机制。
一、多线程之间的通信。
就是多个线程在操作同一份数据, 但是操作的方法不同。
如: 对于同一个存储块,其中有两个存储位:name sex, 现有两个线程,一个向其中存放数据,一个打印其中的数据。
为了解决上述问题中的安全问题(在存放线程进行存放操作的时候, 打印线程不能对共有数据进行操作),所以应当对两个线程
操作共有数据的代码部分进行同步(使用synchronized(),来进行同步, 注意 :使用同一个对象作为同步锁。
二、等待唤醒机制。
在上述案例实现过后运行,会发现:打印结果顺序并不是按照存放顺序进行的, 所以在此处就引入了等待唤醒机制。
解释:就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify())。
在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
所使用的方法(关键字):
对象名.wait(); 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
对象名.notify(); 唤醒在此对象监视器上等待的单个线程。
对象名.notifyAll();唤醒在此对象监视器上等待的所有线程。
注意:以上方法(关键字),都必须使用在同步中,因为其需要对持有该对象监视器所有权(锁)的线程进行操作。
所以必须使用在同步中,因为只有同步中的线程才具有该对象监视器的所有权(锁),
wait()和sleep()一样,都会抛出 InterruptedException 异常。
而且,以上方法都定义在类:Object中
是因为,这些方法在操作同步中的线程的时候,都必须标示其所操作线程所持有的锁(被该锁的对象调用),
而只有同一个对象监视器下(同一个锁上的)的被等待线程,可以被持有该锁的线程唤醒,(无法唤醒不同锁上的线程)
即: 等待和唤醒的必须是同一个对象的监视器下(同一个锁上)的线程。
而锁可以是任意已近确定的对象, 能被任意对象调用的方法应当定义在 Object类中。
监视器(锁): 同一个对象的监视器下(同一个锁上)的线程,一次只能执行一个:就是拥有监视器所有权(持有锁)的那一个线程。
三、接口Lock和 接口Condition
注:在使用Lock 与Condition 时,必须引入其工具包:import java.util.concurrent.locks.*
在jdk1.5以上,为了解决多个线程同时在对一份共享数据进行不同操作时出现的安全问题(要么陷入所有线程同时等待的状态、
要么每次唤醒所有等待线程的问题),
其为我们提供了新的 锁工具: Lock来替换 synchronized , 由 Lock对象所创建的绑定于该Lock对象的 condition对象
替换了object类中的wait,notify操作。
1、Lock接口, 已知实现该接口的类: ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
1)、 ReentrantLock(大体功能与 synchronized相当,不过可以绑定多个condition)
构造方法:ReentrantLock() 创建一个 ReentrantLock 的实例。
ReentrantLock(boolean fair) 创建一个具有给定公平策略的 ReentrantLock
常用方法: void lock() :获取一个锁。
void unlock() :释放该锁。
Condition newCondition() 返回用来与此 Lock 实例一起使用的 Condition 实例。
2、Contition接口, 已知实现类:AbstractQueuedLongSynchronizer.ConditionObject, AbstractQueuedSynchronizer.ConditionObject(暂时不学习)
常用方法:void await() 造成当前线程在接到信号或被中断之前一直处于等待状态。
void signal() 唤醒一个等待线程。
void signalAll() 唤醒所有等待线程。
常见代码示例:
class BoundedBuffer {
final Lock lock = new ReentrantLock(); //创建一个Lock的子类对象 lock
final Condition notFull = lock.newCondition(); //调用lock的newCondition方法,创建一个condition子类对象 notFull
final Condition notEmpty = lock.newCondition(); //调用lock的newCondition方法,创建一个condition子类对象 notEmpty
public void put(Object x) throws InterruptedException { //
lock.lock(); //获取锁
try {
while (判断语句)
notFull.await(); //判断成功,线程等待于notFull下。
操作代码
notEmpty.signal(); //唤醒notEmpty下的等待线程。
} finally { //保证其后语句执行。
lock.unlock(); //释放锁。
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while ()
notEmpty.await();
操作代码
notFull.signal();
} finally {
lock.unlock();
}
}
}
四、停止线程
注:在旧版本的jdk中存在stop方法,但是在新版本中。此方法被过时。
线程停止的原理: 当 run方法中的代码执行完毕过后,就自动停止该线程。
1、当线程中是循环代码的时候, 只要控制住循环结束,就能够结束该线程。
特殊情况:当线程中有wait()语句或者await()等语句时,会使得线程处于冻结状态, 让控制循环结束的代码或者标记无法执行或读取,
那么线程就不会结束。
当所有线程都陷入冻结,没有指定方法解除冻结时,就需要我们强制清除冻结状态,这样就可以操作标记使循环结束。
Thread类中提供了这一方法: Interrupt()方法,(等待型语句本身会有一个InterrptedException异常的判断,
只要被Interrupt方法打断冻结,就会抛出这一异常, 我们就可以在异常处理语句中建立循环结束的标记)。
五、守护线程
Thread类中有一个方法,调用该方法并传入 true ,能将该线程定义为 守护线程(后台线程),
该方法是: void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。(调用时应当传入true)。
注意: 1、将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
(就是说,后台线程依赖于前台线程)
2、该方法必须在启动线程前调用。
示例: Thread t = new Thread();
t.setDaemon(true);
t.start();
此时的线程 t 为守护线程。
六、Join方法。
vpi解释:void join() 等待该线程终止。
在线程A执行的时候,碰到了B线程.join方法时,A线程就会等待,等B线程执行完,才继续执行。
(当在调用join方法时传入参数: long millis 时,表示 A线程等待B线程执行时间最长为millis毫秒。)
join可以用来临时加入线程执行。
JVM
GC
二面
项目的详细细节,项目中用到的框架的原理。
一面二面的面试官都是年轻小哥,加起来问了一个半小时吧,感觉没什么问题
最精彩的是HR面
HR小姐姐上来问了一些基本问题,比如职业规划,课余学习,在学校表现如何等。。
之后,我就是开始作死了
HR:你们学校是什么时候发三方协议呢?
我:大概是10月份吧
HR:你作为计算机专业的学生,有没有特别想去的公司呢?
我:当然是BAT啦,基本上都会想去吧。。
HR:那你现在投了BAT了嘛?
我:投了
HR:进展如何呢?
我:笔试都没开始。。。
HR:那我现在给你发offer的话,之后如果你获得了BAT的offer,你还是会选择去BAT的嘛?
我:是的,还是会去BAT。。(HR话都问到这份上了,我也的确是想想留在斗鱼,毕竟离家近,然而BAT基本上是所有计算机都想进的,我根本没办法厚着脸皮说不去。。)
HR:那如果是美团滴滴这种公司呢?
我:斗鱼离家比较近,如果不是bat还是选择斗鱼。
HR:嗯,好了,我了解了,那个你在等一下,你前面面的还不错,我们总监会过来。
总监面:
全程碾压我,估计是为了压一压我在HR面的嚣张气焰技,术问的比较多,勉勉强强答出来了一部分,最后给了一道题目让设计一个hashmap,可以通过value取key。
然后最后我问总监,这个结果什么时候出来。
总监跟我说,要等秋招结束统一进行排序,才能发offer 。(和我等秋招结束,再选择offer 一个道理)
Emmm,我觉得我hr面说的有点过分,有点悬。