分享

采用Thread.join()或CountDownLatch来实现线程间同步

 时间要去哪 2015-01-19

       对于在Android开发中进行多线程编程来说,线程同步是一个需要经常面对的问题。例如主线程创建了几个子线程来执行复杂的计算,要求所有的子线程执行完后返回结果给主线程,主线程才能继续后续的操作,此时就需要考虑线程同步了。我也是从阅读Android源码中关于相机的部分后才发现有两种方式可以比较容易的实现线程同步,下面一一讲解。


       1.采用Thread.join()函数

   在当前线程中调用thread.join()会导致当前线程阻塞,此时只有当thread线程执行完后,当前线程才能继续往下执行。

  1. package com.android.test;  
  2.   
  3. import java.util.concurrent.CountDownLatch;  
  4. import android.app.Activity;  
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7.   
  8. public class ThreadJoinTest extends Activity {  
  9.     CountDownLatch mCountDownLatch = new CountDownLatch(1);  
  10.       
  11.     @Override  
  12.     protected void onCreate(Bundle savedInstanceState) {  
  13.         super.onCreate(savedInstanceState);  
  14.           
  15.         Thread thread = new Thread(new Runnable() {  
  16.             @Override  
  17.             public void run() {  
  18.                 try {  
  19.                     for(int i = 0; i < 5; i++){  
  20.                         Log.i("com", "thread i = " + i);  
  21.                         Thread.sleep(500);  
  22.                     }  
  23.                 } catch (Exception e) {  
  24.                     // TODO: handle exception  
  25.                 }  
  26.             }  
  27.         });  
  28.           
  29.         thread.start();  
  30.         Log.i("com", "main start");  
  31.         try {  
  32.             thread.join();  
  33.             //thread.join(1500);  
  34.         } catch (InterruptedException e1) {  
  35.             e1.printStackTrace();  
  36.         }  
  37.         Log.i("com", "main end ");  
  38.     }  
  39.   
  40. }  

       程序运行后log如下:

  1. 06-22 21:40:07.953: INFO/com(17958): main start  
  2. 06-22 21:40:07.953: INFO/com(17958): thread i = 0  
  3. 06-22 21:40:08.453: INFO/com(17958): thread i = 1  
  4. 06-22 21:40:08.953: INFO/com(17958): thread i = 2  
  5. 06-22 21:40:09.453: INFO/com(17958): thread i = 3  
  6. 06-22 21:40:09.953: INFO/com(17958): thread i = 4  
  7. 06-22 21:40:10.453: INFO/com(17958): main end   

       从log可以看出,程序执行后,首先主线程打印了"main start",执行到thread.join()后,被阻塞了,此时子线程开始打印log,而且时间间隔都是500毫秒,当子线程执行完后,主线程才最终打印出"main end"。

    有些情况下子线程因为某种原因也可能被阻塞,则此时主线程可能永远得不到继续执行,所以我们可以采用thread.join()的一个重载方法join(long millis),其可以传入一个时间参数,表示等到thread线程执行完或者超过mills时间后才能允许调用这个函数的线程继续往下执行,例如把刚才代码中的join()换成thread.join(1500),则程序运行后log如下:

  1. 06-22 22:12:17.183: INFO/com(19691): main start  
  2. 06-22 22:12:17.183: INFO/com(19691): thread i = 0  
  3. 06-22 22:12:17.683: INFO/com(19691): thread i = 1  
  4. 06-22 22:12:18.183: INFO/com(19691): thread i = 2  
  5. 06-22 22:12:18.683: INFO/com(19691): main end   
  6. 06-22 22:12:18.683: INFO/com(19691): thread i = 3  
  7. 06-22 22:12:19.183: INFO/com(19691): thread i = 4  
       由于子线程执行完的时间超过1500毫秒,所以在thread.join(1500)执行完并等待了1500毫秒后,主线程开始再次执行,注意此时子线程并没有停止,而是跟主线程同时执行。


       2.采用CountDownLatch

   CountDownLatch内部维护一个计数器,初始化的时候会传入一个整型值作为计数器的初始值,其主要包括两个函数,await()和countDown(),当在某个线程中调用了await(),则该线程被阻塞,每调用countDown()则计数器的值减1,只有当计数器为0时刚才被阻塞的线程才能继续执行,这里修改官方上的demo来说明情况。

  1. package com.android.test;  
  2. import java.util.concurrent.CountDownLatch;  
  3. import java.util.concurrent.TimeUnit;  
  4.   
  5. import android.app.Activity;  
  6. import android.os.Bundle;  
  7. import android.util.Log;  
  8.   
  9. public class CountDownLatchTest extends Activity {  
  10.       
  11.     private final int N = 5;  
  12.       
  13.     @Override  
  14.     protected void onCreate(Bundle savedInstanceState) {  
  15.         // TODO Auto-generated method stub  
  16.         super.onCreate(savedInstanceState);  
  17.         CountDownLatch startSignal = new CountDownLatch(1);  
  18.         CountDownLatch doneSignal = new CountDownLatch(N);  
  19.   
  20.          for (int i = 0; i < N; ++i){ // create and start threads  
  21.              new Thread(new Worker(startSignal, doneSignal)).start();  
  22.          }  
  23.            
  24.          Log.i("com", "main thread start");  
  25.          try {  
  26.             Thread.sleep(2000);  
  27.         } catch (InterruptedException e1) {  
  28.             // TODO Auto-generated catch block  
  29.             e1.printStackTrace();  
  30.         }  
  31.         Log.i("com", "main thread doing");  
  32.          startSignal.countDown();      // let all threads proceed  
  33.          try {  
  34.             //doneSignal.await(2,TimeUnit.SECONDS);  
  35.             doneSignal.await();  
  36.         } catch (InterruptedException e) {  
  37.             e.printStackTrace();  
  38.         }  
  39.         Log.i("com", "main thread finish");  
  40.     }  
  41.   
  42.     class Worker implements Runnable {  
  43.            private final CountDownLatch startSignal;  
  44.            private final CountDownLatch doneSignal;  
  45.            Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {  
  46.               this.startSignal = startSignal;  
  47.               this.doneSignal = doneSignal;  
  48.            }  
  49.            public void run() {  
  50.               try {  
  51.                 Log.i("com", "Sub Thread start");  
  52.                 //startSignal.await(2,TimeUnit.SECONDS);  
  53.                 startSignal.await();  
  54.                 doWork();  
  55.                 doneSignal.countDown();  
  56.               } catch (InterruptedException ex) {} // return;  
  57.            }  
  58.   
  59.            void doWork() {   
  60.                Log.i("com", "Sub Thread doing");  
  61.                try {  
  62.                    Thread.sleep(2000);  
  63.                    Log.i("com", "Sub Thread end");  
  64.             } catch (Exception e) {  
  65.                 // TODO: handle exception  
  66.             }  
  67.            }  
  68.      }  
  69. }  

       程序打印log如下:

  1. 06-23 09:04:46.863: INFO/com(28948): Sub Thread start  
  2. 06-23 09:04:46.863: INFO/com(28948): Sub Thread start  
  3. 06-23 09:04:46.863: INFO/com(28948): Sub Thread start  
  4. 06-23 09:04:46.863: INFO/com(28948): Sub Thread start  
  5. 06-23 09:04:46.863: INFO/com(28948): main thread start  
  6. 06-23 09:04:46.863: INFO/com(28948): Sub Thread start  
  7. 06-23 09:04:48.863: INFO/com(28948): main thread doing  
  8. 06-23 09:04:48.863: INFO/com(28948): Sub Thread doing  
  9. 06-23 09:04:48.863: INFO/com(28948): Sub Thread doing  
  10. 06-23 09:04:48.863: INFO/com(28948): Sub Thread doing  
  11. 06-23 09:04:48.863: INFO/com(28948): Sub Thread doing  
  12. 06-23 09:04:48.863: INFO/com(28948): Sub Thread doing  
  13. 06-23 09:04:50.863: INFO/com(28948): Sub Thread end  
  14. 06-23 09:04:50.863: INFO/com(28948): Sub Thread end  
  15. 06-23 09:04:50.863: INFO/com(28948): Sub Thread end  
  16. 06-23 09:04:50.863: INFO/com(28948): Sub Thread end  
  17. 06-23 09:04:50.863: INFO/com(28948): Sub Thread end  
  18. 06-23 09:04:50.863: INFO/com(28948): main thread finish  


       在代码中,尽管所有子线程在创建后立马就启动了,并且主线程调用了Thread.sleep(2000)阻塞自己2秒,可是子线程的log还是在main thread doing之后才开始打印,这正是由于在每个子线程内部都调用了startSignal.await(),所以需要等待主线程调用完startSignal.countDown()才能继续后面的操作。之后主线程中也调用了doneSignal.await(),而doneSignal所设置的计数器为5,所以这时又得等到所有子线程都执行完doneSignal.countDown()后才能打印出main thread finish。这样就达到了主线程和子线程的相互同步。当然await()也有另一个重载函数await (long timeout, TimeUnit unit),其timeout类似join(),不过要比join好的一点是它可以传入一个时间单位TimeUnit,设置时间更灵活点,具体可以参见sdk。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多