最后的内容
在开头跟大家分享的时候我就说,面试我是没有做好准备的,全靠平时的积累,确实有点临时抱佛脚了,以至于我自己还是挺懊恼的。(准备好了或许可以拿个40k,没做准备只有30k+,你们懂那种感觉吗)
如何准备面试?
1、前期铺垫(技术沉积)
程序员面试其实是对于技术的一次摸底考试,你的技术牛逼,那你就是大爷。大厂对于技术的要求主要体现在:基础,原理,深入研究源码,广度,实战五个方面,也只有将原理理论结合实战才能把技术点吃透。
下面是我会看的一些资料笔记,希望能帮助大家由浅入深,由点到面的学习Java,应对大厂面试官的灵魂追问
这部分内容过多,小编只贴出部分内容展示给大家了,见谅见谅!
- Java程序员必看《Java开发核心笔记(华山版)》
- Redis学习笔记
- Java并发编程学习笔记
四部分,详细拆分并发编程——并发编程+模式篇+应用篇+原理篇
- Java程序员必看书籍《深入理解 ava虚拟机第3版》(pdf版)
- 大厂面试必问——数据结构与算法汇集笔记
其他像Spring,SpringBoot,SpringCloud,SpringCloudAlibaba,Dubbo,Zookeeper,Kafka,RocketMQ,RabbitMQ,Netty,MySQL,Docker,K8s等等我都整理好,这里就不一一展示了。
2、狂刷面试题
技术主要是体现在平时的积累实用,面试前准备两个月的时间再好好复习一遍,紧接着就可以刷面试题了,下面这些面试题都是小编精心整理的,贴给大家看看。
①大厂高频45道笔试题(智商题)
②BAT大厂面试总结(部分内容截图)
③面试总结
3、结合实际,修改简历
程序员的简历一定要多下一些功夫,尤其是对一些字眼要再三斟酌,如“精通、熟悉、了解”这三者的区别一定要区分清楚,否则就是在给自己挖坑了。当然不会包装,我可以将我的简历给你参考参考,如果还不够,那下面这些简历模板任你挑选:
以上分享,希望大家可以在金三银四跳槽季找到一份好工作,但千万也记住,技术一定是平时工作种累计或者自学(或报班跟着老师学)通过实战累计的,千万不要临时抱佛脚。
另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。
private transient volatile Node tail;
// UNSAFE对象
private static final sun.misc.Unsafe UNSAFE;
// head字段的偏移量
private static final long headOffset;
// tail字段偏移量
private static final long tailOffset;
public ConcurrentLinkedQueue() {
head = tail = new Node(null);
}
如上构造函数初始化时候会构建一个item
为NULL
的空节点作为链表的首尾节点。
这个方法的作用就是在队列末端添加一个节点,如果传递的参数是null
,就抛出空指针异常,否则由于该队列是无界队列,该方法会一直返回true
,而且该方法使用CAS
算法实现的,所以不会阻塞线程;
// 队列末端添加一个节点
public boolean offer(E e) {
// 如果e为空,那么抛出空指针异常
checkNotNull(e);
// 将传进来的元素封装成一个节点,Node的构造器中调用UNSAFE.putObject(this, itemOffset, item)把e赋值给节点中的item
final Node newNode = new Node(e);
// [1] 这里的for循环从最后的节点开始
for (Node t = tail, p = t;😉 {
Node q = p.next;
// [2] 如果q为null,说明p就是最后的节点了
if (q == null) {
// [3] CAS更新:如果p节点的下一个节点是null,就把写个节点更新为newNode
if (p.casNext(null, newNode)) {
// [4] CAS成功,但是这时p==t,所以不会进入到这里的if里面,直接返回true
// 那么什么时候会走到这里面来呢?其实是要有另外一个线程也在调用offer方法的时候,会进入到这里面来
if (p != t)
casTail(t, newNode);
return true;
}
}
else if (p == q) // [5]
p = (t != (t = tail)) ? t : head;
else // [6]
p = (p != t && t != (t = tail)) ? t : q;
}
}
1、上面执行到[3]
的时候,由于头节点和尾节点默认都是指向哨兵节点的,由于这个时候p
的下一个节点为null
,所以当前线程A
执行CAS
会成功,下图所示;
2、如果此时还有一个线程B
也来尝试[3]
中CAS
,由于此时p
节点的下一个节点不是null
了,于是线程B
会跳到[1]
出进行第二次循环,然后会到[6]
中,由于p
和t
此时是相等的,所以这里是false
,即p=q
,下图所示:
3、然后线程B
又会跳到[1]
处进行第三次循环,由于执行了Node<E> q = p.next
,所以此时q
指向最后的null
,就到了[3]
处进行CAS
,这次是可以成功的,成功之后如下图所示:
4、这个时候因为p!=t
,所以可以进入到[4]
,这里又会进行一个CAS
:如果tail
和t
指向的节点一样,那么就将tail指向新添加的节点,如图所示,这个时候线程B也就执行完了;
5、其实还有[5]
没有走到,这个是在poll
操作之后才执行的,我们先跳过,等说完poll
方法之后再回头看看;另外说一下,add
方法其实就是调用的是offer
方法,就不多说了;
public boolean add(E e) {
return offer(e);
}
这个方法是获取头部的这个节点,如果队列为空则返回null
。
public E poll() {
// 这里其实就是一个goto的标记,用于跳出for循环
restartFromHead:
// [1]
for (;😉 {
for (Node h = head, p = h, q;😉 {
E item = p.item;
// [2] 如果当前节点中存的值不为空,则CAS设置为null
if (item != null && p.casItem(item, null)) {
// [3] CAS成功就更新头节点的位置
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
// [4] 当前队列为空,就返回null
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
// [5] 当前节点和下一个节点一样,说明节点自引用,则重新找头节点
else if (p == q)
continue restartFromHead;
// [6]
else
p = q;
}
}
}
final void updateHead(Node h, Node p) {
if (h != p && casHead(h, p))
h.lazySetNext(h);
}
分为几种情况:
第一种情况是线程A
调用poll
方法的时候,发现队列是空的,即头节点和尾节点都指向哨兵节点,就会直接到[4]
,返回null
。
第二种情况,线程A
执行到了[4]
,此时有一个线程却调用offer
方法添加了一个节点,下图所示,那么此时线程A
就不会走[4]
了,[5]
也不满足,于是会到[6]
这里来,然后线程A
又会跳到[1]
处进行循环,此时p
指向的节点中item
不为null
,所以会到[2]
中;
到了这里还没完,还记不记得offer
方法中有一个地方的代码没有执行的啊!就是这种情况,尾节点自己引用自己,我们再调用offer
会怎么样呢?
回到offer方法,先会到[1]
,然后q
指向自己这个哨兵节点(注意,此时虽然p
指向的节点中存的是null
,但是p!=null
},于是再到[5]
,此时的图如下左图所示;此时由于t==tail
,所以p=head
;
再在offer
方法循环一次,此时q
指向null
,下面左图所示,然后就可以进入[2]
中进行CAS
,CAS
成功,因为此时p!=t
,所以还要进行CAS
将tail
指向新节点,下面右图所示,可以让GC
回收那个垃圾!
这个方法的作用就是获取队列头部的元素,只获取不移除,注意这个方法和上面的poll
方法的区别啊!
public E peek() {
// [1] goto标志
restartFromHead:
for (;😉 {
for (Node h = head, p = h, q;😉 {
// [2]
E item = p.item;
// [3]
if (item != null || (q = p.next) == null) {
updateHead(h, p);
return item;
}
// [4]
else if (p == q)
continue restartFromHead;
// [5]
else
p = q;
}
}
}
final void updateHead(Node h, Node p) {
if (h != p && casHead(h, p))
h.lazySetNext(h);
}
1、如果队列中为空的时候,走到[3]
的时候,就会如下图所示,由于h==p
,所以updateHead
方法啥也不做,然后返回就返回item
为null
。
2、如果队列不为空,那么如下左图所示,此时进入循环内不满足条件,会到[5]
这里,将p
指向q
,然后再进行一次循环到[3]
,将q
指向p
的后一个节点,下面右图所示;
3、然后调用updateHead
方法,用CAS
将头节点指向p
这里,然后将h
自己指向自己,下图所示,最后返回item
获取当前队列元素个数,在并发环境下不是很有用,因为使用CAS
没有加锁,所以从调用size
函数到返回结果期间有可能增删元素,导致统计的元素个数不精确。
public int size() {
int count = 0;
for (Node p = first(); p != null; p = succ§)
if (p.item != null)
// 最大返回Integer.MAX_VALUE
if (++count == Integer.MAX_VALUE)
break;
return count;
}
// 获取第一个队列元素(哨兵元素不算),没有则为null
Node first() {
restartFromHead:
for (;😉 {
for (Node h = head, p = h, q;😉 {
boolean hasItem = (p.item != null);
if (hasItem || (q = p.next) == null) {
updateHead(h, p);
return hasItem ? p : null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
// 获取当前节点的next元素,如果是自引入节点则返回真正头节点
final Node succ(Node p) {
Node next = p.next;
return (p == next) ? head : next;
}
如果队列里面存在该元素则删除给元素,如果存在多个则删除第一个,并返回true
,否者返回false
。
public boolean remove(Object o) {
// 查找元素为空,直接返回false
if (o != null) {
Node next, pred = null;
for (Node p = first(); p != null; pred = p, p = next) {
boolean removed = false;
E item = p.item;
// 相等则使用cas值null,同时一个线程成功,失败的线程循环查找队列中其他元素是否有匹配的。
if (item != null) {
if (!o.equals(item)) {
// 获取next元素
next = succ§;
最后
关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:
- Java基础部分
- 算法与编程
- 数据库部分
- 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)
这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。
作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。
[外链图片转存中…(img-uKJSDTGY-1715608573853)]
- 数据库部分
[外链图片转存中…(img-nLTQRWBJ-1715608573853)]
- 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)
[外链图片转存中…(img-Oq6xp7Dy-1715608573853)]
这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。
作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。