>>分享Android开发相关的技术 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 19533 个阅读者 刷新本主题
 * 贴子主题:  Android线程处理简述 回复文章 点赞(0)  收藏  
作者:mary    发表时间:2020-03-31 17:22:23     消息  查看  搜索  好友  邮件  复制  引用

    Android线程处理简述

           附件工程是有关Android线程的,里面对的&错的方式都有。遇到报错那就对了,先熟悉了,以后才更清楚不是吗^^。

          还有,运行结果就不都截图了,懒人一个T^T。

           一、基础篇

          1)UI线程概念

          Android为单线程模型。当一个程序第一次启动时,Android会自动创建一个对应的主线程(Main Thread)。它负责把事件分派到相应的控件,用于用户与Android控件进行交互。所以通常又被叫做UI线程

          在开发Android应用时必须遵守单线程模型的原则:<u>Android</u><u> UI</u><u>操作并不是线程安全的并且这些操作必须在UI</u><u>线程中执行</u>。

          简单表述为: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.        private  TextView textView;
  4.        /** Runnable */  
  5.        private  Runnable runnable =  new  Runnable() {
  6.            @Override  
  7.            public   void  run() {
  8.               logThreadId();  // 打印当前线程ID  
  9.               textView.setText(tag +  ", I'm Join!" );  // 执行UI操作  
  10.           }
  11.       };
  12.        @Override  
  13.        protected   void  onCreate(Bundle savedInstanceState) {
  14.            super .onCreate(savedInstanceState);
  15.           setContentView(R.layout.ui_thread);
  16.           tag =  "UIThreadActivity" ;
  17.           textView = (TextView) findViewById(R.id.textView);
  18.       }
  19.        /** 1.1 UI线程阻塞 */  
  20.        public   void  blockUi(View v) {
  21.           tag =  "blockUi" ;
  22.           logThreadId();  // 打印当前线程ID  
  23.            /* 读取网页内容并显示 */  
  24.           textView.setText( "开始读取网页!" );  // 该步可能未显示,为什么?  
  25.           StringBuffer document = loadHtml( "http://www.google.com" ); // 耗时操作:读取网页源码
  26.           textView.setText( null  == document ?  "读取失败!"  : document.toString());  // 显示网页源码  
  27.            /**
  28.             * 1.观察按钮呈按下状态持续时间<br>
  29.             * 2.尝试在按钮呈按下状态时,进行按键和触屏操作<br>
  30.             */  
  31.       }
  32.        /** 1.2 非主线程更新UI */  
  33.        public   void  updateUi(View v) {
  34.           tag =  "updateUi" ;
  35.            new  Thread(runnable).start();
  36.       }
  37.        /** 2.1 方法1:Handler */  
  38.        public   void  methodOne(View v) {
  39.           tag =  "methodOne" ;
  40.            // Can't create handler inside thread that has not called  
  41.            // Looper.prepare();  
  42.            final  Handler handler =  new  Handler();
  43.            new  Thread( new  Runnable() {
  44.                @Override  
  45.                public   void  run() {
  46.                   logThreadId();  // 打印当前线程ID  
  47.                   handler.post(runnable);
  48.                    // handler.postDelayed(runnable, 1000);  
  49.               }
  50.           }).start();
  51.       }
  52.        /** 2.2 方法2: View.post(Runnable) */  
  53.        public   void  methodTwo(View v) {
  54.           tag =  "methodTwo" ;
  55.            new  Thread( new  Runnable() {
  56.                @Override  
  57.                public   void  run() {
  58.                   logThreadId();  // 打印当前线程ID  
  59.                   textView.post(runnable);
  60.                    // textView.postDelayed(runnable, 1000);  
  61.               }
  62.           }).start();
  63.       }
  64.        /** 2.3 方法3:Activity.runOnUiThread(Runnable) */  
  65.        public   void  methodThree(View v) {
  66.           tag =  "methodThree" ;
  67.            new  Thread( new  Runnable() {
  68.                @Override  
  69.                public   void  run() {
  70.                   logThreadId();  // 打印当前线程ID  
  71.                   runOnUiThread(runnable);
  72.               }
  73.           }).start();
  74.       }
  75.        /**
  76.         * 读取网页源码
  77.         */  
  78.        private  StringBuffer loadHtml(String urlStr) {
  79.            try  {
  80.               StringBuffer doc =  new  StringBuffer();
  81.               URL url =  new  URL(urlStr);
  82.               URLConnection conn = url.openConnection();
  83.               BufferedReader reader =  new  BufferedReader( new  InputStreamReader(
  84.                       conn.getInputStream()));
  85.               String line =  null ;
  86.                while  ((line = reader.readLine()) !=  null )
  87.                   doc.append(line);
  88.               reader.close();
  89.                return  doc;
  90.           }  catch  (IOException e) {
  91.               e.printStackTrace();
  92.           }
  93.            return   null ;
  94.       }
  95.   }

          亲,记得看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.        // 创建一个LooperThread对象,实现了消息循环  
  5.       LooperThread thread =  new  LooperThread();
  6.        // 必须启动这个线程  
  7.       thread.start();
  8.        // 创建一个消息对象并设置信息  
  9.       Message msg =  new  Message();
  10.       Bundle bundle =  new  Bundle();
  11.       bundle.putString( "key" ,  "1.1 Thread实现消息循环" );
  12.       msg.setData(bundle);
  13.        // 发送消息对象  
  14.       thread.mHandler.sendMessage(msg);
  15.   }
  16.    /** 实现消息循环的线程(在Android索引文档android.os.Looper的概述里有介绍) */  
  17.    private   class  LooperThread  extends  Thread {
  18.        public  Handler mHandler;
  19.        public   void  run() {
  20.           Looper.prepare();
  21.           mHandler =  new  Handler() {
  22.                public   void  handleMessage(Message msg) {
  23.                    // process incoming messages here  
  24.                   logThreadId();  // 打印当前线程ID  
  25.                   Log.e(tag, msg.getData().getString( "key" ));
  26.               }
  27.           };
  28.           Looper.loop();
  29.       }
  30.   }

          如果不使用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.        // 生成一个HandlerThread对象,使用Looper来处理消息队列  
  5.       HandlerThread thread =  new  HandlerThread( "MyThread" );
  6.        // 必须启动这个线程  
  7.       thread.start();
  8.        // 将一个线程绑定到Handler对象上,则该Handler对象就可以处理线程的消息队列  
  9.       MyHandler myhandler =  new  MyHandler(thread.getLooper());
  10.        // 从Handler中获取消息对象  
  11.       Message msg = myhandler.obtainMessage();
  12.        // 设置消息对象信息  
  13.       Bundle bundle =  new  Bundle();
  14.       bundle.putString( "key" ,  "1.2 Handler与Looper相关联" );
  15.       msg.setData(bundle);
  16.        // 将消息对象发送给目标对象Handler  
  17.       msg.sendToTarget();
  18.   }
  19.    /** 重写Handler的消息处理方法 */  
  20.    private   class  MyHandler  extends  Handler {
  21.        // 带有参数的构造函数  
  22.        public  MyHandler(Looper looper) {
  23.            super (looper);
  24.       }
  25.        @Override  
  26.        public   void  handleMessage(Message msg) {
  27.            // process incoming messages here  
  28.           logThreadId();  // 打印当前线程ID  
  29.           Log.e(tag, msg.getData().getString( "key" ));
  30.       }
  31.   }

                    所以,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.    private   class  ProgressThread  extends  Thread {
  19.        private   int  progress;
  20.        public  ProgressThread( int  progress) {
  21.            this .progress = progress;
  22.       }
  23.        @Override  
  24.        public   void  run() {
  25.            try  {
  26.                while  (progress <=  100 ) {
  27.                   progress +=  5 ;  // 进度+5  
  28.                    // doSomething(); // 执行耗时操作  
  29.                   Thread.sleep( 100 );
  30.                    // 从Handler中获取消息对象  
  31.                   Message msg = myHandler.obtainMessage();
  32.                    // 设置消息对象信息  
  33.                   Bundle b =  new  Bundle();
  34.                    // 向Handler发送消息,更新UI  
  35.                   b.putInt( "key" , progress);
  36.                   msg.setData(b);
  37.                   myHandler.sendMessage(msg);
  38.               }
  39.           }  catch  (InterruptedException e) {
  40.               e.printStackTrace();
  41.           }
  42.       }
  43.   }

          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.        private  Context mContext;
  11.        private  ProgressDialog dialog;  // 进度框  
  12.        public  LoadHtmlTask(Context context) {
  13.            this .mContext = context;
  14.           initDialog();  // 初始化进度对话框  
  15.       }
  16.        /** 初始化进度对话框 */  
  17.        private   void  initDialog() {
  18.           dialog =  new  ProgressDialog(mContext);
  19.           dialog.setMax( 100 );  // 设置最大进度值  
  20.           dialog.setTitle( "Loading..." );  // 设置标题  
  21.           dialog.setCancelable( false );  // 设为返回键不可取消  
  22.           dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);  // 设为进度条样式  
  23.            // 增加取消按钮及其事件  
  24.           dialog.setButton( "取消" ,  new  DialogInterface.OnClickListener() {
  25.                public   void  onClick(DialogInterface dialog,  int  i) {
  26.                   dialog.dismiss();  // 取消显示  
  27.                   cancel( true );  // 取消并中断任务  
  28.               }
  29.           });
  30.       }
  31.        /** doInBackground之前,在主线程执行 */  
  32.        @Override  
  33.        protected   void  onPreExecute() {
  34.           logThreadId( "onPreExecute()" );  // 打印当前线程ID  
  35.           dialog.show();  // 显示进度对话框  
  36.       }
  37.        /** onPreExecute()之后,在后台线程执行 */  
  38.        @Override  
  39.        protected  String doInBackground(String... params) {
  40.           logThreadId( "doInBackground" );  // 打印当前线程ID  
  41.            // 未传入参数直接返回  
  42.            if  ( null  == params || params.length <=  0 ) {
  43.                return   "请确认输入了网址参数!" ;
  44.           }
  45.            try  {
  46.                // 创建HttpGet对象,params[0]为url  
  47.               HttpGet httpGet =  new  HttpGet(params[ 0 ]);
  48.                // 发送Http Get请求,并返回HttpResponse对象  
  49.               HttpResponse response =  new  DefaultHttpClient()
  50.                       .execute(httpGet);
  51.                // 判断响应状态,200表示成功响应  
  52.                if  (response.getStatusLine().getStatusCode() ==  200 ) {
  53.                   HttpEntity entity = response.getEntity();  // 获取返回结果  
  54.                    long  length = entity.getContentLength();  // 获取内容长度(google获取不到)  
  55.                    boolean  getLen = length >  0  ?  true  :  false ;  // 判断是否获取了内容长度  
  56.                   InputStream is = entity.getContent();  // 获取响应内容  
  57.                    if  (is !=  null ) {
  58.                       ByteArrayOutputStream baos =  new  ByteArrayOutputStream();
  59.                        byte [] buf =  new   byte [ 128 ];
  60.                        int  ch = - 1 ;
  61.                        long  count =  0 ;
  62.                        while  ((ch = is.read(buf)) != - 1  && !isCancelled()) {  // 同时还未取消  
  63.                           baos.write(buf,  0 , ch);
  64.                           count += ch;
  65.                            if  (getLen) {  // 获取了内容长度时  
  66.                                // 调用publishProgress()更新进度  
  67.                               publishProgress(( int ) ((count / ( float ) length) *  100 ));
  68.                           }
  69.                       }
  70.                       is.close();
  71.                       baos.close();
  72.                        return   new  String(baos.toByteArray());  // 返回结果  
  73.                   }
  74.                    return   "无返回内容!" ;
  75.               }  else  {
  76.                    return   "服务器未响应或失败!" ;
  77.               }
  78.           }  catch  (Exception e) {
  79.               e.printStackTrace();
  80.           }
  81.            return   "程序异常啦!" ;
  82.       }
  83.        /** 调用了publishProgress(),在主线程执行 */  
  84.        @Override  
  85.        protected   void  onProgressUpdate(Integer... values) {
  86.            // logThreadId("onProgressUpdate"); // 打印当前线程ID  
  87.           dialog.setProgress(values[ 0 ]);
  88.       }
  89.        /** 未调用了cancel(),doInBackground()结束后,在主线程执行 */  
  90.        @Override  
  91.        protected   void  onPostExecute(String result) {
  92.           logThreadId( "onPostExecute(result)" );  // 打印当前线程ID  
  93.           dialog.dismiss();  // 取消显示  
  94.           showMsg(result);  // 显示结果  
  95.       }
  96.        /** 调用了cancel(),doInBackground()结束后,在主线程执行 */  
  97.        @Override  
  98.        protected   void  onCancelled() {
  99.           logThreadId( "onCancelled" );  // 打印当前线程ID  
  100.           showMsg( "用户取消了该任务!" );
  101.       }
  102.        /** 提示框显示消息 */  
  103.        private   void  showMsg(String message) {
  104.            // message内容过多时,提示框显示会延迟,例如sina  
  105.            new  AlertDialog.Builder(mContext)
  106.                   .setTitle( "消息" )
  107.                   .setMessage(message)
  108.                   .setNegativeButton( "关闭" ,
  109.                            new  DialogInterface.OnClickListener() {
  110.                                @Override  
  111.                                public   void  onClick(DialogInterface dialog,
  112.                                        int  which) {
  113.                                   dialog.dismiss();
  114.                               }
  115.                           }).show();
  116.       }
  117.   }

           三、扩展篇

1)Service中的Toast

          Service中不能直接使用Toast提示信息,推荐如下方式:

  1.    private  Handler handler;  // Handler  
  2.   Override
  3.    public   void  onCreate() {
  4.       Log.i( "onCreate" ,  "==onCreate==" );
  5.        super .onCreate();
  6.       handler =  new  Handler(Looper.getMainLooper());  // 使用应用的主消息循环  
  7.   }
  8.    /**
  9.     * Toast提示(service中toast不能直接显示)
  10.     */  
  11.    private   void  showToast( final   int  resId,  final  Object... formatArgs) {
  12.       handler.post( new  Runnable() {
  13.            public   void  run() {
  14.               Toast.makeText(getApplicationContext(),
  15.                       getString(resId, formatArgs), Toast.LENGTH_SHORT)
  16.                       .show();
  17.           }
  18.       });
  19.        // 以下方式只能显示一次  
  20.        // Looper.prepare();  
  21.        // Toast.makeText(this, resId, Toast.LENGTH_SHORT).show();  
  22.        // Looper.loop();  
  23.   }

      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.        private   static   final  String TAG =  "TestPool" ;  // 标记  
  3.        private  ExecutorService service;  // 线程池  
  4.        public  TestPool() {
  5.            // service = Executors.newSingleThreadExecutor(); // 创建一个单任务线程池  
  6.           service = Executors.newFixedThreadPool( 3 );  // 创建最多同时运行3个任务线程池  
  7.       }
  8.        /** 增加一个线程任务 */  
  9.        public   void  addTask() {
  10.           service.execute( this );
  11.       }
  12.        /** 线程任务 */  
  13.        @Override  
  14.        public   void  run() {
  15.           log( 1 );  // 显示日志  
  16.            try  {
  17.               Thread.sleep( 5  *  1000 );
  18.           }  catch  (InterruptedException e) {
  19.               e.printStackTrace();
  20.           }
  21.           log( 2 );  // 显示日志  
  22.       }
  23.        /** 显示日志 */  
  24.        private   void  log( int  which) {
  25.           String result =  1  == which ?  ",任务开始!"  :  ",任务结束!" ;
  26.           Log.e(TAG,  "线程:"  + String.valueOf(Thread.currentThread().getId())
  27.                   + result);
  28.       }
  29.   }

           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.        private  Product product;
  3.        private  Consumer ConsumerA, ConsumerB;
  4.        public  TestSync() {
  5.           product =  new  Product();
  6.           ConsumerA =  new  Consumer( "小怪兽A" , product);
  7.           ConsumerB =  new  Consumer( "小怪兽B" , product);
  8.       }
  9.        public   void  produce() {
  10.            synchronized  (product) {
  11.               Log.e( "TestSync" ,  "\\(^o^)/,投掷一个果冻!" );
  12.               product.plus();  // 增加一个产品  
  13.                /* 优先使用notifyAll(),更容易让jvm找到最适合被唤醒的线程 */  
  14.                // product.notify(); // 唤醒一个线程  
  15.               product.notifyAll();  // 唤醒所有线程  
  16.           }
  17.       }
  18.        public   void  start() {
  19.           ConsumerA.start();
  20.           ConsumerB.start();
  21.       }
  22.        public   void  stop() {
  23.           ConsumerA.stopEating();
  24.           ConsumerB.stopEating();
  25.            synchronized  (product) {
  26.               product.notifyAll();  // 唤醒所有线程  
  27.           }
  28.       }
  29.   }
  30.    /** 产品 */  
  31.    class  Product {
  32.        private   int  count =  0 ;  // 产品数量  
  33.        public  Product() {
  34.       }
  35.        /** 是否无产品 */  
  36.        public   boolean  isNull() {
  37.            return  count <=  0  ?  true  :  false ;
  38.       }
  39.        /** 增加产品 */  
  40.        public   void  plus() {
  41.           count++;
  42.       }
  43.        /** 减少产品 */  
  44.        public   void  minus() {
  45.           count--;
  46.       }
  47.   }
  48.    /** 消费者 */  
  49.    class  Consumer  extends  Thread {
  50.        private  String name;  // 消费者  
  51.        private  Product product;  // 产品  
  52.        private   int  count =  0 ;  // 数量  
  53.        private   boolean  waitEating =  true ;  // 标记  
  54.        public  Consumer(String name, Product product) {
  55.            this .name = name;
  56.            this .product = product;
  57.       }
  58.        @Override  
  59.        public   void  run() {
  60.            while  (waitEating) {
  61.                synchronized  (product) {
  62.                    while  (product.isNull() && waitEating) {
  63.                        try  {
  64.                           Log.e(name,  "(ˉ﹃ˉ),等待果冻中..." );
  65.                           product.wait();
  66.                       }  catch  (InterruptedException e) {
  67.                           e.printStackTrace();
  68.                       }
  69.                   }
  70.                    if  (waitEating) {
  71.                       Log.e(name,  "~\\(≧▽≦)/~,抢到了果冻!" );
  72.                       product.minus();
  73.                       count++;
  74.                   }
  75.               }
  76.           }
  77.           Log.e(name,  "(~ o ~)~zZ,吃不下了。计:"  + count);
  78.       }
  79.        public   void  stopEating() {
  80.           waitEating =  false ;
  81.       }
  82.   }

           四、后记

          好吧,最后再截点图--!

              点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

             点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

              点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

                     怎么一直是小怪兽A抢到的果冻?不应该啊T^T。

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

                     附件:http://down.51cto.com/data/2359858
----------------------------
原文链接:https://blog.51cto.com/vaero/782595

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2020-04-18 17:34:47 重新编辑]
  Java面向对象编程-->按面向对象开发的基础范例
  JavaWeb开发-->Web运作原理(Ⅲ)
  JSP与Hibernate开发-->通过JPA API检索数据
  Java网络编程-->RMI框架
  精通Spring-->Vue简介
  Vue3开发-->计算属性和数据监听
  Android的Service和广播的讲解
  Android ListView滑动加载
  实用程序:android 处理图片工具
  Android Gallery实现循环显示图像
  Android ExpandableListView 使用范例
  Android 手势操作GestureDetector
  编译Irrlicht On Android
  在Window中下载和安装Android源代码
  Roboletric+Retrofit2单元测试
  Android使用讯飞SDK开发语音识别及合成小Demo
  Android 内容提供者(Content Provider)
  Android中使用Activity管理类
  Android无侵入式引导提示-一键式启动
  Android——文章详情页的处理
  Android中NDK的含义和作用
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。