这才叫Synchronized的源码分析,Java开发笔试题目

markOop mark = obj->mark(); //获取锁对象的对象头

//判断mark是否为可偏向状态,即mark的偏向锁标志位为1,锁标志位为 01,线程id为null

if (mark->is_biased_anonymously() && !attempt_rebias) {

//这个分支是进行对象的hashCode计算时会进入,在一个非全局安全点进行偏向锁撤销

markOop biased_value       = mark;

//创建一个非偏向的markword

markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());

//Atomic:cmpxchg_ptr是CAS操作,通过cas重新设置偏向锁状态

markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);

if (res_mark == biased_value) {//如果CAS成功,返回偏向锁撤销状态

return BIAS_REVOKED;

}

} else if (mark->has_bias_pattern()) {//如果锁对象为可偏向状态(biased_lock:1, lock:01,不管线程id是否为空),尝试重新偏向

Klass* k = obj->klass();

markOop prototype_header = k->prototype_header();

//如果已经有线程对锁对象进行了全局锁定,则取消偏向锁操作

if (!prototype_header->has_bias_pattern()) {

markOop biased_value       = mark;

//CAS 更新对象头markword为非偏向锁

markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);

assert(!(*(obj->mark_addr()))->has_bias_pattern(), “even if we raced, should still be revoked”);

return BIAS_REVOKED; //返回偏向锁撤销状态

} else if (prototype_header->bias_epoch() != mark->bias_epoch()) {

//如果偏向锁过期,则进入当前分支

if (attempt_rebias) {//如果允许尝试获取偏向锁

assert(THREAD->is_Java_thread(), “”);

markOop biased_value       = mark;

markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());

//通过CAS 操作, 将本线程的 ThreadID 、时间错、分代年龄尝试写入对象头中

markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);

if (res_mark == biased_value) { //CAS成功,则返回撤销和重新偏向状态

return BIAS_REVOKED_AND_REBIASED;

}

} else {//不尝试获取偏向锁,则取消偏向锁

//通过CAS操作更新分代年龄

markOop biased_value       = mark;

markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());

markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);

if (res_mark == biased_value) { //如果CAS操作成功,返回偏向锁撤销状态

return BIAS_REVOKED;

}

}

}

}

…//省略

}

​​​​​​​偏向锁的撤销

当到达一个全局安全点时,这时会根据偏向锁的状态来判断是否需要撤销偏向锁,调用 revoke_at_safepoint方法,这个方法也是在 biasedLocking.cpp中定义的

void BiasedLocking::revoke_at_safepoint(Handle h_obj) {

assert(SafepointSynchronize::is_at_safepoint(), “must only be called while at safepoint”);

oop obj = h_obj();

//更新撤销偏向锁计数,并返回偏向锁撤销次数和偏向次数

HeuristicsResult heuristics = update_heuristics(obj, false);

if (heuristics == HR_SINGLE_REVOKE) {//可偏向且未达到批量处理的阈值(下面会单独解释)

revoke_bias(obj, false, false, NULL); //撤销偏向锁

} else if ((heuristics == HR_BULK_REBIAS) ||

(heuristics == HR_BULK_REVOKE)) {//如果是多次撤销或者多次偏向

//批量撤销

bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);

}

clean_up_cached_monitor_info();

}

​​​​​​​偏向锁的释放,需要等待全局安全点(在这个时间点上没有正在执行的字节码),首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否还活着,如果线程不处于活动状态,则将对象头设置成无锁状态。如果线程仍然活着,则会升级为轻量级锁,遍历偏向对象的所记录。栈帧中的锁记录和对象头的Mark Word要么重新偏向其他线程,要么恢复到无锁,或者标记对象不适合作为偏向锁。最后唤醒暂停的线程。

JVM内部为每个类维护了一个偏向锁revoke计数器,对偏向锁撤销进行计数,当这个值达到指定阈值时,JVM会认为这个类的偏向锁有问题,需要重新偏向(rebias),对所有属于这个类的对象进行重偏向的操作成为 批量重偏向(bulk rebias)。在做bulk rebias时,会对这个类的epoch的值做递增,这个epoch会存储在对象头中的epoch字段。在判断这个对象是否获得偏向锁的条件是:markword的 biased_lock:1、lock:01、threadid和当前线程id相等、epoch字段和所属类的epoch值相同,如果epoch的值不一样,要么就是撤销偏向锁、要么就是rebias; 如果这个类的revoke计数器的值继续增加到一个阈值,那么jvm会认为这个类不适合偏向锁,就需要进行bulk revoke操作

轻量级锁的获取逻辑

轻量级锁的获取,是调用 ::slow_enter方法,该方法同样位于 synchronizer.cpp文件中

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {

markOop mark = obj->mark();

assert(!mark->has_bias_pattern(), “should not see bias pattern here”);

if (mark->is_neutral()) { //如果当前是无锁状态, markword的biase_lock:0,lock:01

//直接把mark保存到BasicLock对象的_displaced_header字段

lock->set_displaced_header(mark);

//通过CAS将mark word更新为指向BasicLock对象的指针,更新成功表示获得了轻量级锁

if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {

TEVENT (slow_enter: release stacklock) ;

return ;

}

// Fall through to inflate() …

}

//如果markword处于加锁状态、且markword中的ptr指针指向当前线程的栈帧,表示为重入操作,不需要争抢锁

else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {

assert(lock != mark->locker(), “must not re-lock the same lock”);

assert(lock != (BasicLock*)obj->mark(), “don’t relock with same BasicLock”);

lock->set_displaced_header(NULL);

return;

}

#if 0

// The following optimization isn’t particularly useful.

if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {

lock->set_displaced_header (NULL) ;

return ;

}

#endif

//代码执行到这里,说明有多个线程竞争轻量级锁,轻量级锁通过inflate进行膨胀升级为重量级锁

lock->set_displaced_header(markOopDesc::unused_mark());

ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);

}

​​​​​​​轻量级锁的获取逻辑简单再整理一下

  1. mark->is_neutral()方法, is_neutral这个方法是在 markOop.hpp中定义,如果 biased_lock:0且lock:01表示无锁状态

  2. 如果mark处于无锁状态,则进入步骤(3),否则执行步骤(5)

  3. 把mark保存到BasicLock对象的displacedheader字段

  4. 通过CAS尝试将markword更新为指向BasicLock对象的指针,如果更新成功,表示竞争到锁,则执行同步代码,否则执行步骤(5)

  5. 如果当前mark处于加锁状态,且mark中的ptr指针指向当前线程的栈帧,则执行同步代码,否则说明有多个线程竞争轻量级锁,轻量级锁需要膨胀升级为重量级锁

轻量级锁的释放逻辑

轻量级锁的释放是通过 monitorexit调用

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))

#ifdef ASSERT

thread->last_frame().interpreter_frame_verify_monitor(elem);

#endif

Handle h_obj(thread, elem->obj());

assert(Universe::heap()->is_in_reserved_or_null(h_obj()),

“must be NULL or an object”);

if (elem == NULL || h_obj()->is_unlocked()) {

THROW(vmSymbols::java_lang_IllegalMonitorStateException());

}

ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);

// Free entry. This must be done here, since a pending exception might be installed on

// exit. If it is not cleared, the exception handling code will try to unlock the monitor again.

elem->set_obj(NULL);

#ifdef ASSERT

thread->last_frame().interpreter_frame_verify_monitor(elem);

#endif

IRT_END

​​​​​​​这段代码中主要是通过 ObjectSynchronizer::slow_exit来执行

void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {

fast_exit (object, lock, THREAD) ;

}

​​​​​​​ObjectSynchronizer::fast_exit的代码如下

void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {

assert(!object->mark()->has_bias_pattern(), “should not see bias pattern here”);

// if displaced header is null, the previous enter is recursive enter, no-op

markOop dhw = lock->displaced_header(); //获取锁对象中的对象头

markOop mark ;

if (dhw == NULL) {

// Recursive stack-lock.

// Diagnostics – Could be: stack-locked, inflating, inflated.

mark = object->mark() ;

assert (!mark->is_neutral(), “invariant”) ;

if (mark->has_locker() && mark != markOopDesc::INFLATING()) {

assert(THREAD->is_lock_owned((address)mark->locker()), “invariant”) ;

}

if (mark->has_monitor()) {

ObjectMonitor * m = mark->monitor() ;

assert(((oop)(m->object()))->mark() == mark, “invariant”) ;

assert(m->is_entered(THREAD), “invariant”) ;

}

return ;

}

mark = object->mark() ; //获取线程栈帧中锁记录(LockRecord)中的markword

// If the object is stack-locked by the current thread, try to

// swing the displaced header from the box back to the mark.

if (mark == (markOop) lock) {

assert (dhw->is_neutral(), “invariant”) ;

//通过CAS尝试将Displaced Mark Word替换回对象头,如果成功,表示锁释放成功。

if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {

TEVENT (fast_exit: release stacklock) ;

return;

}

}

//锁膨胀,调用重量级锁的释放锁方法

ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;

}

​​​​​​​轻量级锁的释放也比较简单,就是将当前线程栈帧中锁记录空间中的Mark Word替换到锁对象的对象头中,如果成功表示锁释放成功。否则,锁膨胀成重量级锁,实现重量级锁的释放锁逻辑

锁膨胀的过程分析

重量级锁是通过对象内部的监视器(monitor)来实现,而monitor的本质是依赖操作系统底层的MutexLock实现的。我们先来看锁的膨胀过程,从前面的分析中已经知道了所膨胀的过程是通过 ObjectSynchronizer::inflate方法实现的,代码如下

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {

// Inflate mutates the heap …

// Relaxing assertion for bug 6320749.

assert (Universe::verify_in_progress() ||

!SafepointSynchronize::is_at_safepoint(), “invariant”) ;

for (;😉 { //通过无意义的循环实现自旋操作

const markOop mark = object->mark() ;

assert (!mark->has_bias_pattern(), “invariant”) ;

if (mark->has_monitor()) {//has_monitor是markOop.hpp中的方法,如果为true表示当前锁已经是重量级锁了

ObjectMonitor * inf = mark->monitor() ;//获得重量级锁的对象监视器直接返回

assert (inf->header()->is_neutral(), “invariant”);

assert (inf->object() == object, “invariant”) ;

assert (ObjectSynchronizer::verify_objmon_isinpool(inf), “monitor is invalid”);

return inf ;

}

if (mark == markOopDesc::INFLATING()) {//膨胀等待,表示存在线程正在膨胀,通过continue进行下一轮的膨胀

TEVENT (Inflate: spin while INFLATING) ;

ReadStableMark(object) ;

continue ;

}

if (mark->has_locker()) {//表示当前锁为轻量级锁,以下是轻量级锁的膨胀逻辑

ObjectMonitor * m = omAlloc (Self) ;//获取一个可用的ObjectMonitor

// Optimistically prepare the objectmonitor - anticipate successful CAS

// We do this before the CAS in order to minimize the length of time

// in which INFLATING appears in the mark.

m->Recycle();

m->_Responsible  = NULL ;

m->OwnerIsThread = 0 ;

m->_recursions   = 0 ;

m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class

/**将object->mark_addr()和mark比较,如果这两个值相等,则将object->mark_addr()

改成markOopDesc::INFLATING(),相等返回是mark,不相等返回的是object->mark_addr()**/

markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;

if (cmp != mark) {//CAS失败

omRelease (Self, m, true) ;//释放监视器

continue ;       // 重试

}

markOop dmw = mark->displaced_mark_helper() ;

assert (dmw->is_neutral(), “invariant”) ;

//CAS成功以后,设置ObjectMonitor相关属性

m->set_header(dmw) ;

m->set_owner(mark->locker());

m->set_object(object);

// TODO-FIXME: assert BasicLock->dhw != 0.

guarantee (object->mark() == markOopDesc::INFLATING(), “invariant”) ;

object->release_set_mark(markOopDesc::encode(m));

if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;

TEVENT(Inflate: overwrite stacklock) ;

if (TraceMonitorInflation) {

if (object->is_instance()) {

ResourceMark rm;

tty->print_cr(“Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s”,

(void *) object, (intptr_t) object->mark(),

object->klass()->external_name());

}

}

return m ; //返回ObjectMonitor

}

//如果是无锁状态

assert (mark->is_neutral(), “invariant”);

ObjectMonitor * m = omAlloc (Self) ; 获取一个可用的ObjectMonitor

//设置ObjectMonitor相关属性

m->Recycle();

m->set_header(mark);

m->set_owner(NULL);

m->set_object(object);

m->OwnerIsThread = 1 ;

m->_recursions   = 0 ;

m->_Responsible  = NULL ;

m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;       // consider: keep metastats by type/class

/**将object->mark_addr()和mark比较,如果这两个值相等,则将object->mark_addr()

改成markOopDesc::encode(m),相等返回是mark,不相等返回的是object->mark_addr()**/

if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {

//CAS失败,说明出现了锁竞争,则释放监视器重行竞争锁

m->set_object (NULL) ;

m->set_owner  (NULL) ;

m->OwnerIsThread = 0 ;

m->Recycle() ;

omRelease (Self, m, true) ;

m = NULL ;

continue ;

// interference - the markword changed - just retry.

// The state-transitions are one-way, so there’s no chance of

// live-lock – “Inflated” is an absorbing state.

}

if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;

TEVENT(Inflate: overwrite neutral) ;

if (TraceMonitorInflation) {

if (object->is_instance()) {

ResourceMark rm;

tty->print_cr(“Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s”,

(void *) object, (intptr_t) object->mark(),

object->klass()->external_name());

}

}

return m ; //返回ObjectMonitor对象

}

}

​​​​​​​锁膨胀的过程稍微有点复杂,整个锁膨胀的过程是通过自旋来完成的,具体的实现逻辑简答总结以下几点

  • 1. mark->has_monitor() 判断如果当前锁对象为重量级锁,也就是lock:10,则执行(2),否则执行(3)

  • 2.通过 mark->monitor获得重量级锁的对象监视器ObjectMonitor并返回,锁膨胀过程结束

  • 3.如果当前锁处于 INFLATING,说明有其他线程在执行锁膨胀,那么当前线程通过自旋等待其他线程锁膨胀完成

  • 4.如果当前是轻量级锁状态 mark->has_locker(),则进行锁膨胀。首先,通过omAlloc方法获得一个可用的ObjectMonitor,并设置初始数据;然后通过CAS将对象头设置为`markOopDesc:INFLATING,表示当前锁正在膨胀,如果CAS失败,继续自旋

  • 5.如果是无锁状态,逻辑类似第4步骤

锁膨胀的过程实际上是获得一个ObjectMonitor对象监视器,而真正抢占锁的逻辑,在 ObjectMonitor::enter方法里面

重量级锁的竞争逻辑

重量级锁的竞争,在 ObjectMonitor::enter方法中,代码文件在 objectMonitor.cpp重量级锁的代码就不一一分析了,简单说一下下面这段代码主要做的几件事

  • 通过CAS将monitor的 _owner字段设置为当前线程,如果设置成功,则直接返回

  • 如果之前的 _owner指向的是当前的线程,说明是重入,执行 _recursions++增加重入次数

  • 如果当前线程获取监视器锁成功,将 _recursions设置为1, _owner设置为当前线程

  • 如果获取锁失败,则等待锁释放

void ATTR ObjectMonitor::enter(TRAPS) {

// The following code is ordered to check the most common cases first

// and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.

Thread * const Self = THREAD ;

void * cur ;

cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;

if (cur == NULL) {//CAS成功

// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.

assert (_recursions == 0   , “invariant”) ;

assert (_owner      == Self, “invariant”) ;

// CONSIDER: set or assert OwnerIsThread == 1

return ;

}

if (cur == Self) {

// TODO-FIXME: check for integer overflow!  BUGID 6557169.

_recursions ++ ;

return ;

}

if (Self->is_lock_owned ((address)cur)) {

assert (_recursions == 0, “internal state error”);

_recursions = 1 ;

// Commute owner from a thread-specific on-stack BasicLockObject address to

// a full-fledged “Thread *”.

_owner = Self ;

OwnerIsThread = 1 ;

return ;

}

// We’ve encountered genuine contention.

assert (Self->_Stalled == 0, “invariant”) ;

Self->_Stalled = intptr_t(this) ;

// Try one round of spinning before enqueueing Self

// and before going through the awkward and expensive state

// transitions.  The following spin is strictly optional …

// Note that if we acquire the monitor from an initial spin

// we forgo posting JVMTI events and firing DTRACE probes.

if (Knob_SpinEarly && TrySpin (Self) > 0) {

assert (_owner == Self      , “invariant”) ;

assert (_recursions == 0    , “invariant”) ;

assert (((oop)(object()))->mark() == markOopDesc::encode(this), “invariant”) ;

Self->_Stalled = 0 ;

return ;

}

assert (_owner != Self          , “invariant”) ;

assert (_succ  != Self          , “invariant”) ;

assert (Self->is_Java_thread()  , “invariant”) ;

JavaThread * jt = (JavaThread *) Self ;

assert (!SafepointSynchronize::is_at_safepoint(), “invariant”) ;

assert (jt->thread_state() != _thread_blocked   , “invariant”) ;

assert (this->object() != NULL  , “invariant”) ;

assert (_count >= 0, “invariant”) ;

// Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().

// Ensure the object-monitor relationship remains stable while there’s contention.

Atomic::inc_ptr(&_count);

EventJavaMonitorEnter event;

{ // Change java thread status to indicate blocked on monitor enter.

JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);

DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);

if (JvmtiExport::should_post_monitor_contended_enter()) {

JvmtiExport::post_monitor_contended_enter(jt, this);

}

OSThreadContendState osts(Self->osthread());

ThreadBlockInVM tbivm(jt);

Self->set_current_pending_monitor(this);

// TODO-FIXME: change the following for(;😉 loop to straight-line code.

for (;😉 {

jt->set_suspend_equivalent();

// cleared by handle_special_suspend_equivalent_condition()

// or java_suspend_self()

EnterI (THREAD) ;

if (!ExitSuspendEquivalent(jt)) break ;

//

// We have acquired the contended monitor, but while we were

// waiting another thread suspended us. We don’t want to enter

// the monitor while suspended because that would surprise the

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

如何快速更新自己的技术积累?

  • 在现有的项目里,深挖技术,比如用到netty可以把相关底层代码和要点都看起来。
  • 如果不知道目前的努力方向,就看自己的领导或公司里技术强的人在学什么。
  • 知道努力方向后不知道该怎么学,就到处去找相关资料然后练习。
  • 学习以后不知道有没有学成,则可以通过面试去检验。

我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目

八年CRUD,疫情备战三个月,三面头条、四面阿里拿offer面经分享

八年CRUD,疫情备战三个月,三面头条、四面阿里拿offer面经分享

jt->set_suspend_equivalent();

// cleared by handle_special_suspend_equivalent_condition()

// or java_suspend_self()

EnterI (THREAD) ;

if (!ExitSuspendEquivalent(jt)) break ;

//

// We have acquired the contended monitor, but while we were

// waiting another thread suspended us. We don’t want to enter

// the monitor while suspended because that would surprise the

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-Uz3Z3pqx-1711881610241)]
[外链图片转存中…(img-kMxZ1cnF-1711881610242)]
[外链图片转存中…(img-JSh50h20-1711881610242)]
[外链图片转存中…(img-wZNDgGvl-1711881610243)]
[外链图片转存中…(img-injO0Ckv-1711881610243)]
[外链图片转存中…(img-Oap3bk5a-1711881610244)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-hIrCpUxd-1711881610244)]

如何快速更新自己的技术积累?

  • 在现有的项目里,深挖技术,比如用到netty可以把相关底层代码和要点都看起来。
  • 如果不知道目前的努力方向,就看自己的领导或公司里技术强的人在学什么。
  • 知道努力方向后不知道该怎么学,就到处去找相关资料然后练习。
  • 学习以后不知道有没有学成,则可以通过面试去检验。

我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目

[外链图片转存中…(img-LLRpfP1m-1711881610245)]

[外链图片转存中…(img-LZQG3ILb-1711881610245)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值