Android线程处理简述

         附件工程是有关Android线程的,里面对的&错的方式都有。遇到报错那就对了,先熟悉了,以后才更清楚不是吗^^。
         还有,运行结果就不都截图了,懒人一个T^T。
 
一、基础篇
1)UI线程概念
         Android为单线程模型。当一个程序第一次启动时,Android会自动创建一个对应的主线程(Main Thread)。它负责把事件分派到相应的控件,用于用户与Android控件进行交互。所以通常又被叫做UI线程
         在开发Android应用时必须遵守单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行
         简单表述为:1、不要阻塞UI线程;2、只能在主线程操作UI。
 
         详见Android帮助文档Dev Guide,左侧栏Processes and Threads。
 
2)UI线程示例
2.1)UI线程阻塞
         不考虑Android单线程模型,将所有任务都在该线程中执行,尤其是某些耗时操作,会使得整个用户界面被阻塞。从用户角度来看,就是按键或触屏后响应很慢甚至毫无响应。而且当应用程序阻塞的时间过长时,Android还会向用户提示一个无响应的对话框(不截了==)。
 
2.2)非主线程更新UI
         1、LogCat会报如下的错误消息:

         Uncaught handler: thread Thread-9 exiting due to uncaught exception。

         android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

         2、Android会向用户提示一个强行关闭的对话框(又不截了==)。
 
2.3)非主线程更新UI
         三种方式:①Handler;②View.post(Runnable);③Activity.runOnUiThread(Runnable)。
 
2.4)好了,开始动手测试吧^^
  1. public class UIThreadActivity extends BaseActivity { 
  2.  
  3.     /** 标签 */ 
  4.     private TextView textView; 
  5.  
  6.     /** Runnable */ 
  7.     private Runnable runnable = new Runnable() { 
  8.  
  9.         @Override 
  10.         public void run() { 
  11.             logThreadId(); // 打印当前线程ID 
  12.             textView.setText(tag + ", I'm Join!"); // 执行UI操作 
  13.         } 
  14.     }; 
  15.  
  16.     @Override 
  17.     protected void onCreate(Bundle savedInstanceState) { 
  18.         super.onCreate(savedInstanceState); 
  19.         setContentView(R.layout.ui_thread); 
  20.         tag = "UIThreadActivity"
  21.         textView = (TextView) findViewById(R.id.textView); 
  22.     } 
  23.  
  24.     /** 1.1 UI线程阻塞 */ 
  25.     public void blockUi(View v) { 
  26.         tag = "blockUi"
  27.         logThreadId(); // 打印当前线程ID 
  28.  
  29.         /* 读取网页内容并显示 */ 
  30.         textView.setText("开始读取网页!"); // 该步可能未显示,为什么? 
  31.         StringBuffer document = loadHtml("http://www.google.com"); // 耗时操作:读取网页源码 
  32.         textView.setText(null == document ? "读取失败!" : document.toString()); // 显示网页源码 
  33.         /** 
  34.          * 1.观察按钮呈按下状态持续时间<br> 
  35.          * 2.尝试在按钮呈按下状态时,进行按键和触屏操作<br> 
  36.          */ 
  37.     } 
  38.  
  39.     /** 1.2 非主线程更新UI */ 
  40.     public void updateUi(View v) { 
  41.         tag = "updateUi"
  42.         new Thread(runnable).start(); 
  43.     } 
  44.  
  45.     /** 2.1 方法1:Handler */ 
  46.     public void methodOne(View v) { 
  47.         tag = "methodOne"
  48.         // Can't create handler inside thread that has not called 
  49.         // Looper.prepare(); 
  50.         final Handler handler = new Handler(); 
  51.         new Thread(new Runnable() { 
  52.             @Override 
  53.             public void run() { 
  54.                 logThreadId(); // 打印当前线程ID 
  55.                 handler.post(runnable); 
  56.                 // handler.postDelayed(runnable, 1000); 
  57.             } 
  58.         }).start(); 
  59.     } 
  60.  
  61.     /** 2.2 方法2: View.post(Runnable) */ 
  62.     public void methodTwo(View v) { 
  63.         tag = "methodTwo"
  64.         new Thread(new Runnable() { 
  65.             @Override 
  66.             public void run() { 
  67.                 logThreadId(); // 打印当前线程ID 
  68.                 textView.post(runnable); 
  69.                 // textView.postDelayed(runnable, 1000); 
  70.             } 
  71.         }).start(); 
  72.     } 
  73.  
  74.     /** 2.3 方法3:Activity.runOnUiThread(Runnable) */ 
  75.     public void methodThree(View v) { 
  76.         tag = "methodThree"
  77.         new Thread(new Runnable() { 
  78.             @Override 
  79.             public void run() { 
  80.                 logThreadId(); // 打印当前线程ID 
  81.                 runOnUiThread(runnable); 
  82.             } 
  83.         }).start(); 
  84.     } 
  85.  
  86.     /** 
  87.      * 读取网页源码 
  88.      */ 
  89.     private StringBuffer loadHtml(String urlStr) { 
  90.         try { 
  91.             StringBuffer doc = new StringBuffer(); 
  92.             URL url = new URL(urlStr); 
  93.             URLConnection conn = url.openConnection(); 
  94.             BufferedReader reader = new BufferedReader(new InputStreamReader( 
  95.                     conn.getInputStream())); 
  96.             String line = null
  97.             while ((line = reader.readLine()) != null
  98.                 doc.append(line); 
  99.             reader.close(); 
  100.             return doc; 
  101.         } catch (IOException e) { 
  102.             e.printStackTrace(); 
  103.         } 
  104.         return null
  105.     } 
  106.  
         亲,记得看Log日志出的线程id哦。(卖下萌,不介意吧?)
 
二、提高篇
1)Android消息处理机制
         熟悉Android开发的可能知道其应用程序都是由消息驱动的。参考了Windows的消息循环机制,Android系统通过Handler、Thread、Looper以及MessageQueue实现了这种消息机制。相关的类都被定义在了package android.os。
         推荐这篇博客咯——解析Android消息处理机制。(这个,那个,还建模==)
 
2)实用的消息处理方式
2.1)Thread实现消息循环
  1. /** 1.1 Thread实现消息循环 */ 
  2. public void methodOne(View v) { 
  3.     tag = "methodOne"
  4.  
  5.     // 创建一个LooperThread对象,实现了消息循环 
  6.     LooperThread thread = new LooperThread(); 
  7.     // 必须启动这个线程 
  8.     thread.start(); 
  9.  
  10.     // 创建一个消息对象并设置信息 
  11.     Message msg = new Message(); 
  12.     Bundle bundle = new Bundle(); 
  13.     bundle.putString("key""1.1 Thread实现消息循环"); 
  14.     msg.setData(bundle); 
  15.     // 发送消息对象 
  16.     thread.mHandler.sendMessage(msg); 
  17.  
  18. /** 实现消息循环的线程(在Android索引文档android.os.Looper的概述里有介绍) */ 
  19. private class LooperThread extends Thread { 
  20.     public Handler mHandler; 
  21.  
  22.     public void run() { 
  23.         Looper.prepare(); 
  24.  
  25.         mHandler = new Handler() { 
  26.             public void handleMessage(Message msg) { 
  27.                 // process incoming messages here 
  28.                 logThreadId(); // 打印当前线程ID 
  29.                 Log.e(tag, msg.getData().getString("key")); 
  30.             } 
  31.         }; 
  32.  
  33.         Looper.loop(); 
  34.     } 
         如果不使用Looper.prepare()及loop()的话,就不能创建Handler将消息处理加入到Looper中。LogCat会报“Can't create handler inside thread that has not called Looper.prepare();”的错误信息。
 
2.2)Handler与Looper相关联
         如果构造一个无参的Handler对象,它将自动与当前运行线程相关联。可以注意Handler相关例子中打印出的当前线程ID信息。
         而与Looper相关联,需要创建一个带参数的Handler对象。注意:此时线程类应该是一个HandlerThread类,一个Looper类的Thread类。
  1. /** 1.2 Handler与Looper相关联 */ 
  2. public void methodTwo(View v) { 
  3.     tag = "methodTwo"
  4.      
  5.     // 生成一个HandlerThread对象,使用Looper来处理消息队列 
  6.     HandlerThread thread = new HandlerThread("MyThread"); 
  7.     // 必须启动这个线程 
  8.     thread.start(); 
  9.     // 将一个线程绑定到Handler对象上,则该Handler对象就可以处理线程的消息队列 
  10.     MyHandler myhandler = new MyHandler(thread.getLooper()); 
  11.     // 从Handler中获取消息对象 
  12.     Message msg = myhandler.obtainMessage(); 
  13.     // 设置消息对象信息 
  14.     Bundle bundle = new Bundle(); 
  15.     bundle.putString("key""1.2 Handler与Looper相关联"); 
  16.     msg.setData(bundle); 
  17.     // 将消息对象发送给目标对象Handler 
  18.     msg.sendToTarget(); 
  19.  
  20. /** 重写Handler的消息处理方法 */ 
  21. private class MyHandler extends Handler { 
  22.  
  23.     // 带有参数的构造函数 
  24.     public MyHandler(Looper looper) { 
  25.         super(looper); 
  26.     } 
  27.  
  28.     @Override 
  29.     public void handleMessage(Message msg) { 
  30.         // process incoming messages here 
  31.         logThreadId(); // 打印当前线程ID 
  32.         Log.e(tag, msg.getData().getString("key")); 
  33.     } 
 
         所以,Wifi的HandlerThread,WifiHandler可以这样实例化(推荐博客有述): 
  1. HandlerThread wifiThread = new HandlerThread("WifiService"); 
  2. wifiThread.start(); 
  3. mWifiHandler = new WifiHandler(wifiThread.getLooper()); 
 
2.3)Hanlder与Thread实现异步
         在新线程中执行耗时操作,结束后通过Handler来更新UI。
  1. /** 1.3 Hanlder与Thread实现异步 */ 
  2. public void methodThree(View v) { 
  3.     tag = "methodThree"
  4.     /* 初始化进度条 */ 
  5.     progressBar.setProgress(0); 
  6.     progressBar.setVisibility(View.VISIBLE); 
  7.     // 新线程执行某操作 
  8.     new ProgressThread(0).start(); 
  9.  
  10. /** 更新UI */ 
  11. private Handler myHandler = new Handler() { 
  12.     @Override 
  13.     public void handleMessage(Message msg) { 
  14.         progressBar.setProgress(msg.getData().getInt("key")); 
  15.     } 
  16. }; 
  17.  
  18. /** 新线程任务 */ 
  19. private class ProgressThread extends Thread { 
  20.  
  21.     private int progress; 
  22.  
  23.     public ProgressThread(int progress) { 
  24.         this.progress = progress; 
  25.     } 
  26.  
  27.     @Override 
  28.     public void run() { 
  29.  
  30.         try { 
  31.             while (progress <= 100) { 
  32.  
  33.                 progress += 5// 进度+5 
  34.  
  35.                 // doSomething(); // 执行耗时操作 
  36.                 Thread.sleep(100); 
  37.  
  38.                 // 从Handler中获取消息对象 
  39.                 Message msg = myHandler.obtainMessage(); 
  40.                 // 设置消息对象信息 
  41.                 Bundle b = new Bundle(); 
  42.                 // 向Handler发送消息,更新UI 
  43.                 b.putInt("key", progress); 
  44.                 msg.setData(b); 
  45.                 myHandler.sendMessage(msg); 
  46.             } 
  47.         } catch (InterruptedException e) { 
  48.             e.printStackTrace(); 
  49.         } 
  50.     } 
 
3)Android异步线程——AsyncTask
         当任务需要复杂操作并频繁更新UI时,上述的非主线程访问UI方法会使得代码结构复杂和难以理解。所以Android1.5提供了一个工具类AsyncTask,用以创建与用户界面交互的长时间任务。也在被定义在了package android.os。
         AsyncTask定义了3种泛型类型: Params, Progress and Result;4个执行步骤:onPreExecute, doInBackground, onProgressUpdate and onPostExecute。
 
3.1)泛型类型
         1) Params,发送给后台任务处理的参数类型
         2) Progress,后台任务过程中发布的进度单位
         3) Result,后台任务结束后返回的结果类型

         想了解泛型类型的话,可参见我的Java泛型应用浅析一文^^。
 
3.2)执行步骤
         1) onPreExecute(),该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。
         2) doInBackground(Params...),将在onPreExecute方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
         3) onProgressUpdate(Progress...),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
         4) onPostExecute(Result),在doInBackground执行完成后,onPostExecute方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread。
 
         onPostExecute的参数为doInBackground的返回值,类型由第三个泛型类型所指定。例子里仅是String,也可以是你自己的实体类、接口什么的。
 
3.3)使用准则
         1) Task的实例必须在UI thread中创建
         2) execute方法必须在UI thread中调用
         3) 不要手动的调用onPreExecute(), onPostExecute(Result),
doInBackground(Params...), onProgressUpdate(Progress...)这几个方法
         4) 该task只能被执行一次,否则多次调用时将会出现异常
 
3.4)任务取消
         一个任务在任何时候都能够通过调用cancel(boolean)而取消。调用这个方法,将使得随后调用的isCancelled()返回true。并且在执行完 doInBackground(Object[])后,会去调用onCancelled(Object)而不再是onPostExecute(Object)方法。
         为了确保任务能够尽快的被取消,可以在doInBackground(Object[])内定期校验isCancelled()的返回值(例如在循环判断中)。
 
3.5)样例程序
  1. /** 2 Android异步线程——AsyncTask */ 
  2. public void methodFour(View v) { 
  3.     tag = "methodFour"
  4.     new LoadHtmlTask(this).execute("http://www.baidu.com"); // 执行读取网页任务 
  5.     // new LoadHtmlTask(this).execute("http://www.google.com"); // 获取不到内容长度 
  6.     // new LoadHtmlTask(this).execute("http://www.sina.com"); // 获取大量网页数据 
  7.  
  8. /** 读取网页任务 */ 
  9. private class LoadHtmlTask extends AsyncTask<String, Integer, String> { 
  10.  
  11.     private Context mContext; 
  12.     private ProgressDialog dialog; // 进度框 
  13.  
  14.     public LoadHtmlTask(Context context) { 
  15.         this.mContext = context; 
  16.         initDialog(); // 初始化进度对话框 
  17.     } 
  18.  
  19.     /** 初始化进度对话框 */ 
  20.     private void initDialog() { 
  21.         dialog = new ProgressDialog(mContext); 
  22.         dialog.setMax(100); // 设置最大进度值 
  23.         dialog.setTitle("Loading..."); // 设置标题 
  24.         dialog.setCancelable(false); // 设为返回键不可取消 
  25.         dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); // 设为进度条样式 
  26.         // 增加取消按钮及其事件 
  27.         dialog.setButton("取消"new DialogInterface.OnClickListener() { 
  28.             public void onClick(DialogInterface dialog, int i) { 
  29.                 dialog.dismiss(); // 取消显示 
  30.                 cancel(true); // 取消并中断任务 
  31.             } 
  32.         }); 
  33.     } 
  34.  
  35.     /** doInBackground之前,在主线程执行 */ 
  36.     @Override 
  37.     protected void onPreExecute() { 
  38.         logThreadId("onPreExecute()"); // 打印当前线程ID 
  39.         dialog.show(); // 显示进度对话框 
  40.     } 
  41.  
  42.     /** onPreExecute()之后,在后台线程执行 */ 
  43.     @Override 
  44.     protected String doInBackground(String... params) { 
  45.         logThreadId("doInBackground"); // 打印当前线程ID 
  46.         // 未传入参数直接返回 
  47.         if (null == params || params.length <= 0) { 
  48.             return "请确认输入了网址参数!"
  49.         } 
  50.         try { 
  51.             // 创建HttpGet对象,params[0]为url 
  52.             HttpGet httpGet = new HttpGet(params[0]); 
  53.  
  54.             // 发送Http Get请求,并返回HttpResponse对象 
  55.             HttpResponse response = new DefaultHttpClient() 
  56.                     .execute(httpGet); 
  57.             // 判断响应状态,200表示成功响应 
  58.             if (response.getStatusLine().getStatusCode() == 200) { 
  59.                 HttpEntity entity = response.getEntity(); // 获取返回结果 
  60.  
  61.                 long length = entity.getContentLength(); // 获取内容长度(google获取不到) 
  62.                 boolean getLen = length > 0 ? true : false// 判断是否获取了内容长度 
  63.  
  64.                 InputStream is = entity.getContent(); // 获取响应内容 
  65.                 if (is != null) { 
  66.                     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
  67.                     byte[] buf = new byte[128]; 
  68.                     int ch = -1
  69.                     long count = 0
  70.                     while ((ch = is.read(buf)) != -1 && !isCancelled()) { // 同时还未取消 
  71.                         baos.write(buf, 0, ch); 
  72.                         count += ch; 
  73.                         if (getLen) { // 获取了内容长度时 
  74.                             // 调用publishProgress()更新进度 
  75.                             publishProgress((int) ((count / (float) length) * 100)); 
  76.                         } 
  77.                     } 
  78.                     is.close(); 
  79.                     baos.close(); 
  80.                     return new String(baos.toByteArray()); // 返回结果 
  81.                 } 
  82.                 return "无返回内容!"
  83.             } else { 
  84.                 return "服务器未响应或失败!"
  85.             } 
  86.         } catch (Exception e) { 
  87.             e.printStackTrace(); 
  88.         } 
  89.         return "程序异常啦!"
  90.     } 
  91.  
  92.     /** 调用了publishProgress(),在主线程执行 */ 
  93.     @Override 
  94.     protected void onProgressUpdate(Integer... values) { 
  95.         // logThreadId("onProgressUpdate"); // 打印当前线程ID 
  96.         dialog.setProgress(values[0]); 
  97.     } 
  98.  
  99.     /** 未调用了cancel(),doInBackground()结束后,在主线程执行 */ 
  100.     @Override 
  101.     protected void onPostExecute(String result) { 
  102.         logThreadId("onPostExecute(result)"); // 打印当前线程ID 
  103.         dialog.dismiss(); // 取消显示 
  104.         showMsg(result); // 显示结果 
  105.     } 
  106.  
  107.     /** 调用了cancel(),doInBackground()结束后,在主线程执行 */ 
  108.     @Override 
  109.     protected void onCancelled() { 
  110.         logThreadId("onCancelled"); // 打印当前线程ID 
  111.         showMsg("用户取消了该任务!"); 
  112.     } 
  113.  
  114.     /** 提示框显示消息 */ 
  115.     private void showMsg(String message) { 
  116.  
  117.         // message内容过多时,提示框显示会延迟,例如sina 
  118.         new AlertDialog.Builder(mContext) 
  119.                 .setTitle("消息"
  120.                 .setMessage(message) 
  121.                 .setNegativeButton("关闭"
  122.                         new DialogInterface.OnClickListener() { 
  123.                             @Override 
  124.                             public void onClick(DialogInterface dialog, 
  125.                                     int which) { 
  126.                                 dialog.dismiss(); 
  127.                             } 
  128.                         }).show(); 
  129.     } 
  130.  
 
三、扩展篇
1)Service中的Toast
         Service中不能直接使用Toast提示信息,推荐如下方式:
  1. private Handler handler; // Handler 
  2.  
  3. Override 
  4. public void onCreate() { 
  5.     Log.i("onCreate""==onCreate=="); 
  6.     super.onCreate(); 
  7.  
  8.     handler = new Handler(Looper.getMainLooper()); // 使用应用的主消息循环 
  9.  
  10. /** 
  11.  * Toast提示(service中toast不能直接显示) 
  12.  */ 
  13. private void showToast(final int resId, final Object... formatArgs) { 
  14.     handler.post(new Runnable() { 
  15.         public void run() { 
  16.             Toast.makeText(getApplicationContext(), 
  17.                     getString(resId, formatArgs), Toast.LENGTH_SHORT) 
  18.                     .show(); 
  19.         } 
  20.     }); 
  21.     // 以下方式只能显示一次 
  22.     // Looper.prepare(); 
  23.     // Toast.makeText(this, resId, Toast.LENGTH_SHORT).show(); 
  24.     // Looper.loop(); 

2)Java线程池
         一些简单常用的线程池,只需使用Executors类里面提供了一些静态工厂,如下:
         1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
         2)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
         3)newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
         4)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
         5)newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
         或者使用ThreadPoolExecutor,更加定制化地构造线程池。它们都被定义在package java.util.concurrent。
 
         线程池技术是为了减少频繁创建和销毁线程的系统开销,适用情况有:1)单个任务时间很短、处理请求巨大;2)有突发性大量任务请求;3)需要迅速响应的性能要求等。
 
  1. public class TestPool implements Runnable { 
  2.  
  3.     private static final String TAG = "TestPool"// 标记 
  4.  
  5.     private ExecutorService service; // 线程池 
  6.  
  7.     public TestPool() { 
  8.         // service = Executors.newSingleThreadExecutor(); // 创建一个单任务线程池 
  9.         service = Executors.newFixedThreadPool(3); // 创建最多同时运行3个任务线程池 
  10.     } 
  11.  
  12.     /** 增加一个线程任务 */ 
  13.     public void addTask() { 
  14.         service.execute(this); 
  15.     } 
  16.  
  17.     /** 线程任务 */ 
  18.     @Override 
  19.     public void run() { 
  20.         log(1); // 显示日志 
  21.         try { 
  22.             Thread.sleep(5 * 1000); 
  23.         } catch (InterruptedException e) { 
  24.             e.printStackTrace(); 
  25.         } 
  26.         log(2); // 显示日志 
  27.     } 
  28.  
  29.     /** 显示日志 */ 
  30.     private void log(int which) { 
  31.         String result = 1 == which ? ",任务开始!" : ",任务结束!"
  32.         Log.e(TAG, "线程:" + String.valueOf(Thread.currentThread().getId()) 
  33.                 + result); 
  34.     } 
  35.  
 
3)Java线程同步
         多线程并发访问同一数据时,就会有同步的需求。Java内在的同步机制包含:同步块(或方法)和 volatile 变量。同步块(或方法)通过synchronized关键字声明,而volatile可被看做是轻量级的synchronized。
 
         Java为每个object分配了一个monitor,相关方法如下:
         1)obj.wait()方法将使本线程挂起,并释放obj对象的monitor。只有其他线程调用obj对象的notify()或notifyAll()时,才可以被唤醒。
         2)obj.notifyAll()方法唤醒所有该obj对象相关的沉睡线程,然后被唤醒的众多线程开始竞争obj对象的monitor占有权,最终得到的那个线程会继续执行下去,但其他线程还将继续等待。
         3)obj.notify()方法是随机唤醒一个沉睡线程。
         4)wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用。
 
         没多线程不需要同步,有多线程不一定需要同步。
 
  1. public class TestSync { 
  2.  
  3.     private Product product; 
  4.     private Consumer ConsumerA, ConsumerB; 
  5.  
  6.     public TestSync() { 
  7.         product = new Product(); 
  8.         ConsumerA = new Consumer("小怪兽A", product); 
  9.         ConsumerB = new Consumer("小怪兽B", product); 
  10.     } 
  11.  
  12.     public void produce() { 
  13.         synchronized (product) { 
  14.  
  15.             Log.e("TestSync""\\(^o^)/,投掷一个果冻!"); 
  16.             product.plus(); // 增加一个产品 
  17.  
  18.             /* 优先使用notifyAll(),更容易让jvm找到最适合被唤醒的线程 */ 
  19.  
  20.             // product.notify(); // 唤醒一个线程 
  21.             product.notifyAll(); // 唤醒所有线程 
  22.         } 
  23.     } 
  24.  
  25.     public void start() { 
  26.         ConsumerA.start(); 
  27.         ConsumerB.start(); 
  28.     } 
  29.  
  30.     public void stop() { 
  31.         ConsumerA.stopEating(); 
  32.         ConsumerB.stopEating(); 
  33.  
  34.         synchronized (product) { 
  35.             product.notifyAll(); // 唤醒所有线程 
  36.         } 
  37.     } 
  38.  
  39.  
  40. /** 产品 */ 
  41. class Product { 
  42.  
  43.     private int count = 0// 产品数量 
  44.  
  45.     public Product() { 
  46.     } 
  47.  
  48.     /** 是否无产品 */ 
  49.     public boolean isNull() { 
  50.         return count <= 0 ? true : false
  51.     } 
  52.  
  53.     /** 增加产品 */ 
  54.     public void plus() { 
  55.         count++; 
  56.     } 
  57.  
  58.     /** 减少产品 */ 
  59.     public void minus() { 
  60.         count--; 
  61.     } 
  62.  
  63.  
  64. /** 消费者 */ 
  65. class Consumer extends Thread { 
  66.  
  67.     private String name; // 消费者 
  68.     private Product product; // 产品 
  69.     private int count = 0// 数量 
  70.  
  71.     private boolean waitEating = true// 标记 
  72.  
  73.     public Consumer(String name, Product product) { 
  74.         this.name = name; 
  75.         this.product = product; 
  76.     } 
  77.  
  78.     @Override 
  79.     public void run() { 
  80.         while (waitEating) { 
  81.  
  82.             synchronized (product) { 
  83.  
  84.                 while (product.isNull() && waitEating) { 
  85.                     try { 
  86.                         Log.e(name, "(¯﹃¯),等待果冻中..."); 
  87.                         product.wait(); 
  88.                     } catch (InterruptedException e) { 
  89.                         e.printStackTrace(); 
  90.                     } 
  91.                 } 
  92.  
  93.                 if (waitEating) { 
  94.                     Log.e(name, "~\\(≧▽≦)/~,抢到了果冻!"); 
  95.                     product.minus(); 
  96.                     count++; 
  97.                 } 
  98.             } 
  99.         } 
  100.         Log.e(name, "(~ o ~)~zZ,吃不下了。计:" + count); 
  101.     } 
  102.  
  103.     public void stopEating() { 
  104.         waitEating = false
  105.     } 
  106.  
 
四、后记
         好吧,最后再截点图--!
 
 
main.png main.png
 

main.png

 
         怎么一直是小怪兽A抢到的果冻?不应该啊T^T。
 
         ps:UI线程不太好阻塞,可以找个门户网站的主页(全是图==)、或者观察延迟时间。那个问题 “该步可能未显示,为什么?”不知道可以试下看看^^,为什么呢?