本文转载自微信公众号《编了一个程序》,作者Yasinx。转载本文请联系编辑程序公众号。Y说今天没什么好说的。就我个人而言,我喜欢拍摄天空。放一张我前段时间晚上拍的照片吧。join方法是否释放锁?前段时间有读者私信问了这样一个问题:在wait方法内部调用了Thread实例的join方法,wait方法会释放锁。为什么网上有很多文章(包括我们之前的开源书《深入浅出Java多线程》)都会说join方法不释放锁呢?释放线程对象锁我们先看书上的一个例子:sleep(1000);System.out.println("我是子线程,我睡了一秒");}catch(InterruptedExceptione){e.printStackTrace();}}}publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(newThreadA());thread.start();thread.join();System.out.println("如果你不加join方法,我先把它打出来,它会如果你添加它会有所不同");}}在这个例子中,我们调用了线程。它是一个子线程。我睡了一会。如果我不加join方法,我会先被打出来。如果我添加它,它会有所不同。这个例子的意图很简单。就是利用线程实例的join方法达到主线程等待线程线程执行完后继续执行的效果。join方法底层是如何实现这个功能的呢?它会释放锁吗?我们点进去看看源码。如果(millis==0){while(isAlive()){wait(0);}}else{while(isAlive()){longdelay=millis-now;if(delay<=0){break;}wait(delay);now=System.currentTimeMillis()-base;}}可以看到join最底层是调用的wait(long)方法。wait方法是Object类型的实例方法,会释放当前Object的锁,需要获取当前Object的锁。这可能有点曲折。众所周知,“Java的锁其实本质上是一个对象锁”,因为我们前面调用了thread.join(),所以这里的“锁”对象其实就是线程对象。那么这里wait释放的就是线程的对象锁。我们把上面的main方法简单改一下,用另外一个线程来占用线程对象锁,更直观:(()->{//把线程对象作为锁来占用,这样后面的join中的wait只能在锁释放后才能执行synchronized(thread){try{System.out.println("我占用线程锁");Thread.sleep(10000);System.out.println("我的线程锁被释放");}catch(InterruptedExceptione){e.printStackTrace();}}}).start();thread.join();System.out.println("如果不加join方法,我先打出来,加了就不一样了");}打印结果:我是子-thread,我先睡一秒我是子线程睡一秒后,我释放线程锁。如果我不加join方法,我会先被打出来。如果我添加它,它会有所不同。这就印证了一句话:wait方法执行之前,需要获取当前对象的锁。那么回到最初的问题:join()方法会释放锁吗?严进的回答是会释放线程实例的对象锁,但不会释放其他对象锁(包括主线程)。Stackoverflow对此也有讨论:DoesThread.join()releasethelock?还是继续持有?。简单的说,你说它释放锁是对的,因为它确实通过wait方法释放了线程对象锁,你说它不释放锁也是对的,因为从调用线程的角度来看,它不会释放当前调用线程持有的对象锁。当然,为了不让其他读者看到这里产生这个疑惑,我直接把文中这句话删掉了。^image.png^谁醒了?看到源码,又有新的疑问:join方法里面是一个while循环。wait释放锁的时候,必须要有人把它唤醒,程序才能继续往下走。一定有调用线程对象的notify方法的地方。我们可以在Thread类中找到一个exit()方法,上面的注释是这样写的:这个方法被系统调用,让Thread在真正退出之前有机会进行清理。这么简单的英文大家应该都能听懂。?有这么一段代码:if(group!=null){group.threadTerminated(this);group=null;}voidthreadTerminated(Threadt){synchronized(this){remove(t);if(nthreads==0){notifyAll();}if(daemon&&(nthreads==0)&&(nUnstartedThreads==0)&&(ngroups==0)){destroy();}}}一开始以为是这里被唤醒了,但是仔细一看,这里调用的对象是ThreadGroup的实例,而不是线程实例。所以应该不是这个地方。通过谷歌后,我在stackoverflow(stackoverflow,yyds)上找到了正确答案:whoandwhennotifythethread.wait()whenthread.join()iscalled?答案表明这是在JVM级别做的Things:staticvoidensure_join(JavaThread*thread){//WedonotneedtograptheThreads_lock,sinceweareoperatingonourself.HandlethreadObj(thread,thread->threadObj());assert(threadObj.not_null(),"javathreadobjectmustexist");ObjectLockerlock(threadObj,thread);//Ignorependingexception(ThreadDeath),sinceweareexitinganywaythread->clear_pending_exception();//线程退出.Sosetthread_statusfieldinjava.lang.ThreadclasstoTERMINATED.java_lang_Thread::set_thread_status(threadObj(),java_lang_Thread::TERMINATED);//clearthenativethreadinstance-thismakesisAlivereturnfalseandallowsthejoin()//tocompleteoncewe'vedonethenotify_allbelowjava_lang_Thread::set_thread(threadObj(),NULL);lock.notify_all(thread);//Ignorependingexception(ThreadDeath),sincewearareexitinganywaythread->clear_pending_exception();}可以看到除了notify_all,它其实做了很多收尾工作包括处理异常,设置线程状态等等,如果线程没有启动,那就改代码。如果线程没有通过start启动会怎样?Threadthread=newThread(newThreadA());//thread.start();thread.join();System.out.println("如果不加join方法,我就先打出来了,会如果加上就不一样”);会直接执行最后一行代码打印出来,看join源码就知道了,wait之前会有isAlive()判断,看当前线程是否存活.如果没有start,直接返回false,不进入wait,总结一下,join方法会释放线程对象锁,最底层是wait方法,在JVM层面由notify_all唤醒。
