Java多线程的调度_动力节点Java学院整理
有多个线程,如何控制它们执行的先后次序?
方法一:设置线程优先级。
java.lang.Thread提供了setPriority(intnewPriority)方法来设置线程的优先级,但线程的优先级是无法保障线程的执行次序的,优先级只是提高了优先级高的线程获取CPU资源的概率。也就是说,这个方法不靠谱。
方法二:使用线程合并。
使用java.lang.Thread的join()方法。比如有线程a,现在当前线程想等待a执行完之后再往下执行,那就可以使用a.join()。一旦线程使用了a.join(),那么当前线程会一直等待a消亡之后才会继续执行。什么时候a消亡?a的run()方法执行结束了a就消亡了。
这个方法可以有效地进行线程调度,但却只能局限于等待一个线程的执行调度。如果要等待N个线程的话,显然是无能为力了。而且等待线程必须在被等待线程消亡后才得到继续执行的指令,无法做到两个线程真正意义上的并发,灵活性较差。
方法三:使用线程通信。
java.lang.Object提供了可以进行线程间通信的wait与notify、notifyAll等方法。每个Java对象都有一个隐性的线程锁的概念,通过这个线程锁的概念我们让线程间可以进行通信,各线程不再埋头单干。著名的“生产者-消费者”模型就是基于这个原理实现的。
这个方法也可以有效地进行线程调度,而且也不仅仅局限于等待一个线程的执行调度,具有很大程度上的灵活性。但操作复杂,不易控制容易造成混乱,程序维护起来也不太方便。
方法四:使用闭锁。
闭锁就像一扇门,在先决条件未达成之前这扇门是闭着的,线程无法通过,先决条件达成之后,闭锁打开,线程就可以继续执行了。java.util.concurrent.CountDownLatch是一个很实用的闭锁实现,它提供了countDown()和await()方法达成线程执行队列,这个方法最适合M个线程等待N个线程执行结束再执行的情况。首先初始化一个CountDownLatch对象,比如CountDownLatchdoneSignal=newCountDownLatch(N);该对象具有N作为计数阀值,每个被等待线程通过对doneSignal对象的持有,使用countDown()可以将doneSignal的计数阀值减一;每个等待线程通过对doneSignal对象的持有,使用await()阻塞当前线程,直到doneSignal计数阀值减为0,才继续往下执行。
这个方法也可以有效地进行线程调度,而且比方法三更易于管理,开发者只需控制好CountDownLatch即可。但线程执行次序管理相对单一,它只是指出当前等待线程的数量,而且CountDownLatch的初始阀值一旦设置就只能递减下去,无法重置。如需递减过程中进行阀值的重置可以参考java.util.concurrent.CyclicBarrier。
不管如何,CountDownLatch对于一定条件下的线程队列的达成还是很有用的。对于复杂环境下的线程管理还是卓有成效的。所以熟悉和把握对它的使用还是很有必要的。
以下是一个实际项目中CountDownLatch的使用的例子:
privateMapafterDecryptFilePathMap=newHashMap ();//TODO注意容器垃圾数据的清理工作 classDecryptRunnableimplementsRunnable{ privateServerFileBeanserverFile; privateLongfid;//指向解密文件 privateCountDownLatchdecryptSignal; protectedDecryptRunnable(Longfid,ServerFileBeanserverFile,CountDownLatchdecryptSignal){ this.fid=fid; this.serverFile=serverFile; this.decryptSignal=decryptSignal; } @Override publicvoidrun(){ //开始解密 StringafterDecryptFilePath=null; DecryptSignalAndPathdecryptSignalAndPath=newDecryptSignalAndPath(); decryptSignalAndPath.setDecryptSignal(decryptSignal); afterDecryptFilePathMap.put(fid,decryptSignalAndPath); afterDecryptFilePath=decryptFile(serverFile); decryptSignalAndPath.setAfterDecryptFilePath(afterDecryptFilePath); decryptSignal.countDown();//通知所有阻塞的线程 } } classDecryptSignalAndPath{ privateStringafterDecryptFilePath; privateCountDownLatchdecryptSignal; publicStringgetAfterDecryptFilePath(){ returnafterDecryptFilePath; } publicvoidsetAfterDecryptFilePath(StringafterDecryptFilePath){ this.afterDecryptFilePath=afterDecryptFilePath; } publicCountDownLatchgetDecryptSignal(){ returndecryptSignal; } publicvoidsetDecryptSignal(CountDownLatchdecryptSignal){ this.decryptSignal=decryptSignal; } }
需要先执行的,被等待线程在这里加入:
CountDownLatchdecryptSignal=newCountDownLatch(1); newThread(newDecryptRunnable(fid,serverFile,decryptSignal)).start();//无需拿到新线程句柄,由CountDownLatch自行跟踪 try{ decryptSignal.await(); }catch(InterruptedExceptione){ //TODOAuto-generatedcatchblock }
需要后执行,等待的线程可以这样加入:
CountDownLatchdecryptSignal=afterDecryptFilePathMap.get(fid).getDecryptSignal(); try{ decryptSignal.await(); }catch(InterruptedExceptione){ //TODOAuto-generatedcatchblock }
当然,这也仅仅只是一个简单的CountDownLatch的使用展示,对于CountDownLatch来说有点大材小用了,因为它可以胜任更复杂的多线程环境。示例中的案例完全可以使用线程通信进行搞定。因为CountDownLatch的阀值初始为1,所以这里甚至完全可以使用方法二所说的线程的合并进行取代。
如果读者觉得以上示例不够清晰,也可以参考JDKAPI提供的demo,这个清晰明了:
classDriver2{//... voidmain()throwsInterruptedException{ CountDownLatchdoneSignal=newCountDownLatch(N); Executore=... for(inti=0;i以上所述是小编给大家介绍的Java多线程的调度,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!