当前位置: 首页 > 科技观察

LockSupport:一个非常灵活的线程工具类

时间:2023-03-13 17:55:58 科技观察

LockSupport是一个编程工具类,主要是阻塞和唤醒线程。我们可以用它来实现很多功能。今天主要是对这个工具类的讲解。希望对您有所帮助:一、LockSupport简介1、什么是LockSupport?开头说了,LockSupport是一个线程工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,任意位置唤醒。它里面其实主要有两个方法:park(停放和阻塞线程)和unpark(启动和唤醒线程)。//(1)阻塞当前线程publicstaticvoidpark(Objectblocker);//(2)挂起当前线程,有超时publicstaticvoidparkNanos(Objectblocker,longnanos);//(3)挂起当前线程直到一定时间publicstaticvoidparkUntil(Objectblocker,longdeadline);//(4)无限挂起当前线程publicstaticvoidpark();//(5)挂起当前线程,但是有超时限制publicstaticvoidparkNanos(longnanos);//(6)暂停当前??线程直到一定时间publicstaticvoidparkUntil(longdeadline);//(7)恢复当前线程publicstaticvoidunpark(Threadthread);publicstaticObjectgetBlocker(线程);注意上面123个方法都有一个blocker,这个blocker是用来记录线程被阻塞的时候是谁阻塞的。线程监控和分析工具用来定位原因。现在我们知道LockSupport是用来阻塞和唤醒线程的,相信大家都知道wait/notify也是用来阻塞和唤醒线程的。与之相比,LockSupport有哪些优势呢?2.与wait/notify的比较为了比较,假设你已经了解了wait/notify的机制。不懂的可以上网搜一下,很简单。相信既然你学会了这个LockSupport,相信你也已经学会了wait/notifyadvance。先举一个用例:;}}publicstaticvoidmain(String[]args){MyThreadt1=newMyThread();t1.start();System.out.println("t1已启动,但内部停放");LockSupport.unpark(t1);System.out.println("LockSupporthasbeenunparked");}}上面代码的意思是我们定义了一个线程,但是它内部是parked的,所以需要unpark才能唤醒继续执行,但是上面,我们Park在MyThread,在主线程中unpark。从这个角度看,它似乎和wait/notify没什么区别。那么区别是什么呢?这需要仔细观察。这里主要有两点:(1)wait和notify都是Object中的方法,在调用这两个方法之前必须先获取lock对象,但是park可以在不获取对象锁的情况下对线程进行加锁。(2)notify只能随机选择一个线程唤醒,不能唤醒指定线程,unpark可以唤醒指定线程。区别就是这两个,主要从park和unpark的角度来解释。既然这个LockSupport这么强大,那我们就来看看它的源码吧。二、源码分析(基于jdk1.8)1、park方法publicstaticvoidpark(Objectblocker){Threadt=Thread.currentThread();setBlocker(t,blocker);UNSAFE.park(false,0L);setBlocker(t,无效的);}blocker用于记录线程被阻塞时是谁阻塞的。线程监控和分析工具用来定位原因。setBlocker(t,blocker)方法的作用是记录t线程被broker阻塞。所以我们只关注核心方法,也就是UNSAFE.park(false,0L)。UNSAFE是一个非常强大的类。它的操作是基于底层的,即可以直接操作内存,那么我们从JVM的角度来分析一下:每个java线程都有一个Parker实例:classParker:publicos::PlatformParker{private:volatileint_counter;...public:voidpark(boolisAbsolute,jlong??time);voidunpark();...}classPlatformParker:publicCHeapObj{protected:pthread_mutex_t_mutex[1];pthread_cond_t_cond[1];...}我们从不同的角度理解停放和取消停放观点,你可以考虑一下。unpark其实就相当于一个license,告诉特定的线程你可以停止。当一个特定的线程想要停停停停时,它可以立即停止并在看到许可后立即继续运行。.所以它的执行顺序是可以颠倒的。有了这个概念,我们就来体验一下上面JVM层面的park方法,counter字段就是用来记录所谓的“权限”的。本小部分总结来自:https://www.jianshu.com/p/1f16b838ccd8调用park时,先尝试是否能直接拿到“license”,即当_counter>0时,如果成功,则把_计数器设置为0,并返回。voidParker::park(boolisAbsolute,jlong??time){//理想情况下我们会在旋转时做一些有用的事情,例如//调用unpackTime()。//可选的快速路径检查://Returnimmediatelyifapermitisavailable.//WedeponAtomic::xchg()havingfullbarrierssemantics.upa_faredoingAtomic::xchg(0,&_counter)>0)返回;如果不成功,构造一个ThreadBlockInVM,然后检查_counter是否>0,如果是,设置_counter为0,解锁mutex并返回:ThreadBlockInVMtbivm(jt);//nowaitneededif(_counter>0){_counter=0;status=pthread_mutex_unlock(_mutex);否则判断等待时间,然后调用pthread_cond_wait函数等待。如果等待返回,设置_counter为0,解锁mutex并返回:if(time==0){status=pthread_cond_wait(_cond,_mutex);}_counter=0;status=pthread_mutex_unlock(_mutex);assert_status(status==0,status,"invariant");OrderAccess::fence();这就是park的全过程,概括起来就是消费“牌照”的过程。2、unpark还是先来看一下JDK源码:/***Makesavailablethepermitforthegiventhread,ifit*wasnotalreadyavailable。,或者{@codenull},inwhichcase*thisoperationhasnoeffect*/publicstaticvoidunpark(Threadthread){if(thread!=null)UNSAFE.unpark(thread);}上面注释的意思是给线程生产许可。unparking的时候就简单多了,直接把_counter设置为1,然后unlockmutext就可以返回了。如果_counter之前的值为0,调用pthread_cond_signal唤醒在park中等待的线程:voidParker::unpark(){ints,status;status=pthread_mutex_lock(_mutex);assert(status==0,"invariant");s=_counter;_counter=1;if(s<1){if(WorkAroundNPTLTimedWaitHang){status=pthread_cond_signal(_cond);assert(status==0,"invariant");status=pthread_mutex_unlock(_mutex);assert(status==0,"不变");}else{status=pthread_mutex_unlock(_mutex);assert(status==0,"invariant");status=pthread_cond_signal(_cond);assert(status==0,"invariant");}}else{pthread_mutex_unlock(_mutex);assert(status==0,"invariant");}}ok,现在我们分析完了源码,整个过程其实就是productionlicense和consumptionlicense的过程。而且这个制作过程是可以反过来的。即先生产后消费。下面我们用几个例子来验证一波。三、使用LockSupport1.先中断后停放操作");System.out.println("Interrupted:"+Thread.currentThread().isInterrupted());}}publicstaticvoidmain(String[]args){MyThreadt1=newMyThread();t1.start();System.out.println("t1线程已启动,但停在内部LockSupport中");t1.interrupt();System.out.println("主线程结束");}}我们看一下结果:2.先Unpark然后parkpublicstaticclassMyThreadextendsThread{@Overridepublicvoidrun(){try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(getName()+"EnterThread");锁定支持。公园();System.out.println("Endofoperation");}}我们只需要在park前sleep1秒,保证unpark先执行。本文转载自微信公众号“愚公要移山”,可关注下方二维码。转载本文请联系愚公移山公众号。