Java 多线程编程之八:多线程的调度

来源:互联网 发布:python smtp ssl qq 编辑:程序博客网 时间:2024/06/06 11:01
 

Java 多线程编程之八:多线程的调度

分类: 多线程编程 1138人阅读 评论(0) 收藏 举报
        本博客是“Java 多线程编程”系列的后续篇。“Java 多线程编程”系列其他博客请参阅本博客结尾部分。
        有多个线程,如何控制它们执行的先后次序?
        方法一:设置线程优先级
        java.lang.Thread 提供了 setPriority(int newPriority) 方法来设置线程的优先级,但线程的优先级是无法保障线程的执行次序的,优先级只是提高了优先级高的线程获取 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 对象,比如 CountDownLatch doneSignal = new CountDownLatch(N);该对象具有 N 作为计数阀值,每个被等待线程通过对 doneSignal 对象的持有,使用 countDown() 可以将 doneSignal 的计数阀值减一;每个等待线程通过对 doneSignal 对象的持有,使用 await() 阻塞当前线程,直到 doneSignal 计数阀值减为 0,才继续往下执行。
        这个方法也可以有效地进行线程调度,而且比方法三更易于管理,开发者只需控制好 CountDownLatch 即可。但线程执行次序管理相对单一,它只是指出当前等待线程的数量,而且 CountDownLatch 的初始阀值一旦设置就只能递减下去,无法重置。如需递减过程中进行阀值的重置可以参考 java.util.concurrent.CyclicBarrier。
        不管如何,CountDownLatch 对于一定条件下的线程队列的达成还是很有用的。对于复杂环境下的线程管理还是卓有成效的。所以熟悉和把握对它的使用还是很有必要的。

        以下是一个实际项目中 CountDownLatch 的使用的例子:

[java] view plaincopyprint?
  1. private Map<Long,DecryptSignalAndPath> afterDecryptFilePathMap = new HashMap<Long,DecryptSignalAndPath>();//TODO 注意容器垃圾数据的清理工作  
  2. class DecryptRunnable implements Runnable {  
  3.     private ServerFileBean serverFile;  
  4.     private Long fid;//指向解密文件  
  5.     private CountDownLatch decryptSignal;  
  6.     protected DecryptRunnable(Long fid, ServerFileBean serverFile, CountDownLatch decryptSignal) {  
  7.         this.fid = fid;  
  8.         this.serverFile = serverFile;  
  9.         this.decryptSignal = decryptSignal;  
  10.     }  
  11.     @Override  
  12.     public void run() {  
  13.         //开始解密  
  14.         String afterDecryptFilePath = null;  
  15.         DecryptSignalAndPath decryptSignalAndPath = new DecryptSignalAndPath();  
  16.         decryptSignalAndPath.setDecryptSignal(decryptSignal);  
  17.         afterDecryptFilePathMap.put(fid, decryptSignalAndPath);  
  18.         afterDecryptFilePath = decryptFile(serverFile);  
  19.         decryptSignalAndPath.setAfterDecryptFilePath(afterDecryptFilePath);  
  20.         decryptSignal.countDown();//通知所有阻塞的线程  
  21.     }  
  22.       
  23. }  
  24. class DecryptSignalAndPath {  
  25.     private String afterDecryptFilePath;  
  26.     private CountDownLatch decryptSignal;  
  27.     public String getAfterDecryptFilePath() {  
  28.         return afterDecryptFilePath;  
  29.     }  
  30.     public void setAfterDecryptFilePath(String afterDecryptFilePath) {  
  31.         this.afterDecryptFilePath = afterDecryptFilePath;  
  32.     }  
  33.     public CountDownLatch getDecryptSignal() {  
  34.         return decryptSignal;  
  35.     }  
  36.     public void setDecryptSignal(CountDownLatch decryptSignal) {  
  37.         this.decryptSignal = decryptSignal;  
  38.     }  
  39. }  

        需要先执行的,被等待线程在这里加入:

[java] view plaincopyprint?
  1. CountDownLatch decryptSignal = new CountDownLatch(1);  
  2. new Thread(new DecryptRunnable(fid, serverFile, decryptSignal)).start();//无需拿到新线程句柄,由 CountDownLatch 自行跟踪  
  3. try {  
  4.     decryptSignal.await();  
  5. catch (InterruptedException e) {  
  6.     // TODO Auto-generated catch block  
  7. }  

        需要后执行,等待的线程可以这样加入:

[java] view plaincopyprint?
  1. CountDownLatch decryptSignal = afterDecryptFilePathMap.get(fid).getDecryptSignal();  
  2.   
  3. try {  
  4.     decryptSignal.await();  
  5. catch (InterruptedException e) {  
  6.     // TODO Auto-generated catch block  
  7. }  

        当然,这也仅仅只是一个简单的 CountDownLatch 的使用展示,对于 CountDownLatch 来说有点大材小用了,因为它可以胜任更复杂的多线程环境。示例中的案例完全可以使用线程通信进行搞定。因为 CountDownLatch 的阀值初始为 1,所以这里甚至完全可以使用方法二所说的线程的合并进行取代。

        如果读者觉得以上示例不够清晰,也可以参考 JDK API 提供的 demo,这个清晰明了:

[java] view plaincopyprint?
  1. class Driver2 { // ...  
  2.   void main() throws InterruptedException {  
  3.     CountDownLatch doneSignal = new CountDownLatch(N);  
  4.     Executor e = ...  
  5.   
  6.     for (int i = 0; i < N; ++i) // create and start threads  
  7.       e.execute(new WorkerRunnable(doneSignal, i));  
  8.   
  9.     doneSignal.await();           // wait for all to finish  
  10.   }  
  11. }  
  12.   
  13. class WorkerRunnable implements Runnable {  
  14.   private final CountDownLatch doneSignal;  
  15.   private final int i;  
  16.   WorkerRunnable(CountDownLatch doneSignal, int i) {  
  17.      this.doneSignal = doneSignal;  
  18.      this.i = i;  
  19.   }  
  20.   public void run() {  
  21.      try {  
  22.        doWork(i);  
  23.        doneSignal.countDown();  
  24.      } catch (InterruptedException ex) {} // return;  
  25.   }  
  26.   
  27.   void doWork() { ... }  
  28. }  

        “Java 多线程编程”系列其他博客:
        Java 多线程编程之一 进程与线程,并发和并行的区别:吃馒头的比喻
        Java 多线程编程之二 volatile 关键字的使用
        Java 多线程编程之三:synchronized 关键字的使用
        Java 多线程编程之四:获取 Java VM 中当前运行的所有线程
        Java 多线程编程之五:一个理解 wait() 与 notify() 的例子
        Java 多线程编程之六:线程之间的通信(附源代码)
        Java 多线程编程之七:死锁(附源代码)
0 0
原创粉丝点击