又是一年毕业季,想起去年毕业的自己,一年这么快过去了,从17年实习来北京,现在也快2年了,总结回忆一下之前面试过或者问过别人的面试题,还有一些学习生活感触,希望能对有即将毕业的小伙伴有些帮助,共勉。
面试必问的知识点
- 集合框架(都要看,并且都要看源码),基础知识(看面试宝典,不能死记硬背,需要理解)
- 框架(Spring必问,部分源码,SSM,MVC思想,Mybatis与Hibernate区别,如何选择)
- 数据库(数据库容易忽视,笔试题常用查询要会,面试会问优化,索引,存储过程等,还有维护,备份也需要了解)
- 算法题(多刷牛客网等刷题网站,很重要,平时多写代码)
- 数据结构(常见的理解,一般结合算法题);计算机网络操作系统了解最好;前端技术(会用就好,重要的是使用方法)
- 多线程高并发(这个必问);海量数据处理(问的也很多);设计模式(熟悉常用的就好)
- 心态很重要,找工作不要慌,机会多多。好的心态才能从容面对!
- 简历上写得一定要会
引言
推荐两本自己在看的书,1.大型网站技术架构-核心原理与案例分析。@李智慧大牛的书,2.大型网站系统与java中间件实践。作者: 曾宪杰(原淘宝技术,现在蘑菇街),能拓宽想法和思路,开阔眼界,然后再推荐一个网站并发编程网 - ifeve.com,上边有很多文章讲解的不错。
面试准备
编程技巧
递归控制,循环控制,边界控制,数据结构,树的遍历
面向对象思想
类与对象,接口与实现,继承与封装,不可变类型,泛型
设计模式
Singleton,变继承关系为组合关系,对象如何创建
其他
并行计算,多线程,资源管理
操作系统
是管理和控制计算机硬件与软件资源的计算机程序
进程vs线程
一个进程有多个线程,进程中包括内存和文件/网络句柄
线程是一个栈,和PC,PC指向的是当前的指令,指令放在内存中,指针共享内存,分配栈
进程是一个容器,线程才是CPU调度的基本单位,进程间交互通过TCP/IP的交互,分配内存
存储和寻址
硬盘,内存,缓存,寄存器由慢到快,谷歌把互联网放到了内存里
32位机器的寻址空间是32次方=32x32 = 4G; 64位机器是10^19Bytes; 64位
物理内存中空闲的内存由算法解析分页给虚拟内存
寻址过程: 由指针p先到逻辑内存进程独立2^32或2^64再到物理内存,最后再到寄存器
线程是程序的多个顺序的流动动态执行,线程不能够独立执行,必须依存在应用程序之中,由应用程序提供多个线程执行控制
JOIN关键字
对于数据库以及SQL相关的面试题,这里只能介绍一部分,更多的关于MySQL数据库的索引,引擎,SQL优化方面需要自行学习。
内连接仅显示共有的部分:
SELECT * FROM 'product' AS p join 'category' AS c on p.'categoryId' = c.'categoryId';左外连接显示左表和共有部分:
SELECT * FROM 'product' AS p left join 'category' AS c on p.'categoryId' = c.'categoryId';GROUP BY关键字
SELECT p.'categoryId','categoryName', COUNT(*) FROM 'product' AS p LEFT JOIN 'category' AS c ON p.'category' = c.'category' GROUP BY p.'categoryId','categoryName';查询最少钱数的商品:
SELECT p.*, cat_min.categoryName FROM 'product' AS p JOIN( SELECT p.'categoryId','categoryName',MIN(p.'price') AS min_price FROM 'product' AS p LEFT JOIN 'category' AS c ON p.'categoryId' = c.'categoryId' GROUP BY p.'categoryId','categoryName') AS cat_min ON p.'categoryId' = cat_min.categoryId where p.'price' = cat_min.min_price;事务和乐观锁
ACID
- Atomicity: 原子性,一个事务保证要么全部完成要么全部不完成
- Consistency: 约束,事务前后数据的完整性必须保持一致
- Isolation: 各事务相互独立,互不影响(关键)
- Durability: 事务的持久性
进行读写分离,使用FOR UPDATE 锁机制耗费资源
SELECT count FROM 'product' WHERE 'productId' = 2 FOR UPDATE;
UPDATE 'product' SET 'count' = 47 WHERE 'productId' = 2; SELECT count FROM 'product' WHERE 'productId' = 2; COMMIT ;乐观锁
读取数据,记录Timestamp,通过添加版本的控制,保证同一个改变只写进去一次
SELECT count FROM 'product' WHERE 'productId' = 2; 写不进去的改变将输出
UPDATE 'product' SET 'count' = 45 WHERE 'productId' = 2 AND 'count' = 46;那些方式可以用来程序调优
- 改善数据访问方式以提升缓存命中率
- 使用数据库连接池替代直接的数据库访问
- 使用迭代替代递归
- 合并多个远程调用批量发送
- 共享冗余数据提高访问效率
程序设计语言基础
类型检查 编译时: C,C++,Java,GO… 运行时: Python,Perl,JavaScript,Ruby …
运行/编译 编译为机器代码运行: C,C++ … 编译为中间代码,在虚拟机运行: Java,C# … 解释执行: Python,Perl,JavaScript …
编程范式 面向过程: C,Visual Basic,… 面向对象: Java,C#,C++,Scala,… 函数式: Haskell,Erlang,…
并行计算
将数据拆分到每个节点上,每个节点并行的计算出结果,将结果汇总
外部排序
归并排序属于稳定排序,将数据分为左右两边,分别归并排序,再把两个有序数据
归并
堆结构完全二叉树,根节点最小的数值,先左节点再右节点
k路归并 - PriorityQueue 使用Iterable
可以不断获取下一个元素的能力,元素存储/获取方式被抽象,与归根节点无关/Interable
归并数据源来自Iterable
多线程
死锁分析

synchronized(from)->别的线程在等待from synchronized(to)->别的线程已经锁住了to
死锁条件必须同时满足
互斥等待(必须要有锁)、hold and wait、循环等待、无法剥夺的等待(无超时释放情况)
死锁防止
破除互斥等待 ->一般无法破除; hold and wait ->一次性获取所有的资源; 破除循环等待 -> 按顺序获取资源; 破除无法剥夺 -> 加入超时
最好的方式是hold and wait 不要拿着from的锁再去拿to的锁,可以一次同时拿到两个锁,但是一般不允许这样,那就先拿from的锁然后去拿to的锁,拿不到就放掉from的锁,不断尝试
Synchronized同步静态方法和非静态方法区别
1.Synchronized修饰非静态方法,实际上是对调用该方法的对象加锁,俗称”对象锁”
同一个对象在两个线程中分别访问该对象的两个同步方法,会产生互斥,锁针对的是对象,当对象调用一个synchronized方法,其他同步方法需要等待其执行结束释放才行
不同对象在两个线程中调用同一个同步方法,不会产生互斥,可以并发执行,因为每个线程调用方法的时候都是new一个对象,出现两个空间,两把钥匙
2.Synchronized修饰静态方法,实际上是对该类对象加锁,俗称”类锁”
用类直接在两个线程中调用两个不同的同步方法,会产生互斥,因为对静态对象加锁实际上对类(.class)加锁,类对象只有一个,所有访问同步方法之间一定是互斥的
一个对象在两个线程中分别调用一个静态同步方法和一个非静态同步方法,不会产出互斥,因为产生锁的类型不同,会并发执行
sleep和wait的区别
- 两个方法来自不同的类,分别是Thread和Object
- 最主要的是sleep方法没有释放锁,而wait方法释放了锁,使得敏感词线程可以同步控制块或者方法
- wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用;
- sleep必须要捕获异常,而wait和notify不需要
线程池
创建线程开销大,调用堆栈,操作系统需要建立数据结构来维护线程状态,比创建一个进程小得多,进程包括很多线程和内存空间
线程池: 预先建立好线程,等待任务派发 BlockingQueue阻塞队列
线程池的参数: corePoolSize: 线程池中初始线程数量,可能处于等待状态 maximumPoolSize: 线程池中最大允许线程数量 keepAliveTime: 超过corePoolSize部分线程如果等待这些时间将会回收
Executors 目前提供了 5 种不同的线程池创建配置:
- newCachedThreadPool(),它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列。
- newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目nThreads。
- newSingleThreadExecutor(),它的特点在于工作线程数目被限制为1,操作一个无界的工作队列,所以它保证了所有任务都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。
- newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
- newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java8才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。
Java并发包提供了哪些并发工具类
我们通常所说的并发包也就是java.util.concurrent及其子包,集中了Java并发的各种基础工具类,具体主要包括几个方面:
- 提供了比synchronized更加高级的各种同步结构,包括CountDownLatch,CyclicBarrier,Semaphore等,可以实现更加丰富的多线程操作,比如利用Semaphore作为资源控制器,限制同时进行工作的线程数量。
- 各种线程安全的容器,比如最常见的ConcurrentHashMap、有序的ConcurrentSkipListMap,或者通过类似快照机制,实现线程安全的动态数组CopyOnWriteArrayList等。
- 各种并发队列实现,如各种BlockedQueue实现,比较典型的ArrayBlockingQueue、SynchorousQueue或针对特定场景的PriorityBlockingQueue等。
- 强大的Executor框架,可以创建各种不同类型的线程池,调度任务运行等,绝大部分情况下,不再需要自己从头实现线程池和任务调度器。
未完待续
对于Java面试前段部分就这么多,下次总结好了再分享其他的部分,有问题欢迎沟通 ^_^