Android中的Synchronize关键字

来源:互联网 发布:手机淘宝 假的 编辑:程序博客网 时间:2024/06/01 22:01

虽是师兄网上down的代码,仍需斟酌一番。项目中涉及到在一个Activity里下载并显示Server的图像,从代码上看在两处出现了Synchronized关键字。因为这个代码我觉得很经典,所以在这里贴上两个java文件的关键代码部分,并详细阐述一下,毕竟是花了一天才读懂(开窍较慢...)。

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题栏setContentView(R.layout.detailquery);sys=SysApplication.getInstance();sys.addActivity(this);loadData();initView();imageLoader=new AsynImageLoader();}public void refreshView(){//设置显示截图时if(SettingActivity.toggle_state==true){    imageLoader.showImageAsyn(iv_show, branchInfo.get("image_url"), R.drawable.loadfailed_pic);}//设置取消显示截图时else{   imageLoader.showImageAsyn(iv_show, R.drawable.loadfailed_pic);}}
这是一个Activity的代码,里面涉及到下载和显示图片的关键代码就是红笔标出部分。这里并无异样,关键在下面。

package com.util;public class AsynImageLoader {private static final String TAG = "AsynImageLoader";// 缓存下载过的图片的Mapprivate Map<String, SoftReference<Bitmap>> caches;// 任务队列private List<Task> taskQueue;private boolean isRunning = false;public AsynImageLoader(){// 初始化变量caches = new HashMap<String, SoftReference<Bitmap>>();//令指向Bitmap对象的引用都是软引用taskQueue = new ArrayList<AsynImageLoader.Task>();// 启动图片下载线程isRunning = true;new Thread(runnable).start();//在构造方法中直接开启新线程}public void destroy(){isRunning = false;}/** *  * @param imageView 需要延迟加载图片的对象 * @param url 图片的URL地址 * @param resId 图片加载过程中显示的图片资源 *///                             image_view    url     R.drawable.failurepublic void showImageAsyn(ImageView imageView, String url, int resId){imageView.setTag(url);ImageCallback imagecallback=getImageCallback(imageView, resId);Bitmap bitmap = loadImageAsyn(url, imagecallback);if(bitmap == null){imageView.setImageResource(resId);}else{imageView.setImageBitmap(bitmap);}}public void showImageAsyn(ImageView imageView ,int resId){imageView.setImageResource(resId);}public Bitmap loadImageAsyn(String url, ImageCallback callback){// 判断缓存中是否已经存在该图片if(caches.containsKey(url)==true){// 取出软引用SoftReference<Bitmap> rf = caches.get(url);// 通过软引用,获取图片Bitmap bitmap = rf.get();// 如果该图片已经被释放,则将该path对应的键从Map中移除掉if(bitmap == null){caches.remove(url);}else{// 如果图片未被释放,直接返回该图片return bitmap;}}else{// 如果缓存中不存在该图片,则创建图片下载任务Task task = new Task();task.path = url;task.callback = callback;if(taskQueue.contains(task)==false){taskQueue.add(task);// 唤醒任务下载队列synchronized (runnable) {Log.i("AAAD ", Thread.currentThread().getName());//此时在主线程里,由于主线程                                        runnable.notify();//在此释放一个信号,之前一直处在等待状态的新线程重获了runnable的锁,可以重新跑起来了}}}// 缓存中没有图片则返回nullreturn null;}/** *  * @param imageView  * @param resId 图片加载完成前显示的图片资源ID * @return */private ImageCallback getImageCallback(final ImageView imageView, final int resId){return new ImageCallback() {@Overridepublic void setImage(String path, Bitmap bitmap) {//在主线程对其调用时,loadImage()这一回调方法并未立即执行,//而是task对这个方法进行回调,且时间上十分靠后!!!//Log.i("path_callback", String.valueOf(path.hashCode()));if(path.equals(imageView.getTag().toString())){imageView.setImageBitmap(bitmap);}else{imageView.setImageResource(resId);}}};}private Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg) {// 子线程中返回的下载完成的任务Task task = (Task)msg.obj;// 调用callback对象的loadImage方法,并将图片路径和图片回传给adapter//Log.i("path_close", String.valueOf(System.currentTimeMillis()));//与path_callback_time同时task.callback.setImage(task.path, task.bitmap);//在这里进行的赋值么???//Log.i("time image_loader1", String.valueOf(System.currentTimeMillis()));}};private Runnable runnable = new Runnable() {@Overridepublic void run() {while(isRunning){//新线程开启// 当队列中有未处理的任务时,执行下载任务。仅在下载队列里有任务时,才开启下载过程。while(taskQueue.size() > 0){// 获取第一个任务,并将之从任务队列中删除Task task = taskQueue.remove(0);// 将下载的图片添加到缓存//Log.i("path_task", String.valueOf(task.path.hashCode()));//比path_callback打印得早//Log.i("path_task_time", String.valueOf(System.currentTimeMillis()));task.bitmap = PicUtil.getbitmap(task.path);caches.put(task.path, new SoftReference<Bitmap>(task.bitmap));if(handler != null){// 创建消息对象,并将完成的任务添加到消息对象中Message msg = handler.obtainMessage();msg.obj = task;// 发送消息回主线程handler.sendMessage(msg);}//Log.i("time image_loader2", String.valueOf(System.currentTimeMillis()));}//如果队列无下载任务,则令线程等待synchronized (this) {//this指的啥?指的是调用这个run(){}的对象(这里就是runnable),也就是说如果一个AsynImageLoader对象想在几个线程里想调用run()方法,但若//taskQueue.size()>0不满足而转至次时,须同步。try {//Log.i("time image_loader3", String.valueOf(System.currentTimeMillis()));Log.i("AAAD2", Thread.currentThread().getName());//在新线程里runnable.wait();//释放当前线程(是新建的)对runnable的锁(即监控权),但一直监听runnable.notify();//Log.i("ZZZ", "this.wait()之后 继续执行");//此句不执行} catch (InterruptedException e) {e.printStackTrace();} }}}};//回调接口public interface ImageCallback{void setImage(String path, Bitmap bitmap);}class Task{// 下载任务的下载路径String path;// 下载的图片Bitmap bitmap;// 回调对象ImageCallback callback;@Overridepublic boolean equals(Object o) {Task task = (Task)o;return task.path.equals(path);}}}


先从大概上说说为什么要在这里搞Synchronized(线程间同步)。我们的目的很清楚:就是从Server里下载图片,显示到UI上。通常,这是分为两步的:1.得到图片的url;2.根据url下载图片。这两步应该在两个新线程里进行,但由于二者在逻辑上的关系,显然是下载图片的工作要等获取url工作顺利结束后再开始,这就是两个线程间的同步问题了。在这篇博文里(http://bbs.9ria.com/thread-238040-1-1.html),对同步锁和notify(),wait(), notifyAll()有比较详尽的描述,另外,在http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html这篇博客,博主对锁、Synchronize块的比喻很恰当,推荐一看。但下面我想补充一下我自己看SDK/API的收获:

      .wait(),是Object对象的方法,简单的说,其功能就是让当前线程改为‘等待状态’,我理解其为一种‘具有监听能力的暂停状态’。‘监听’谁?监听同一object的.notify()方法。先停一下,有几点要补充:当A线程里执行了object.wait()时,这个A也就交出了该object的monitor(也就是失去了该对象的锁),被谁拿走?被处于等待池里的若干线程中的一个拿走了(谁能出头拿走这个锁由JVM选定)。A还能拿回该object的锁么?在唯二线程里,必然能拿回,因为没有新建第三个线程,也就不存在‘争抢’的情况,这也是本例中的情况;当线程数≥3时,就不一定能拿回来了(JVM进行二选一,不排除其一很倒霉以致一次也抢不到的情况);从另一角度讲,A也必须交出monitor,否则别的Thread如何能在Synchronized块里进行.notify()呢?在本例中,由于只存在mainThread和一个自定义Thread,情况简单了很多。下面分析代码:

   在Activity中,首先通过loadData从Server拿到了图片的url(这涉及到别的java文件,不详述),然后新建AsynImageLoader对象并另一个imageLoader引用指向它,此时AsynImageLoader的构造方法直接开启了一个新线程Thread01,由于下载队列为空,故令Thread01等待(等待啥?等待其它线程的runnable.notify()或.notifyall),同时交出runnable的monitor。主线程继续走,执行refreshView()方法,关键的一步:imageLoader.showImageAsyn(),该方法下有一步得到回调接口实例的代码,当初引起我不小的困惑:getImageCallback(imageView, resId);当时的困惑是:在返回一个匿名内部类的时候,其下的setImage()方法中的形参path是如何赋值的呢?谁给它赋的呢?其实,单从

 ImageCallback imagecallback=getImageCallback(imageView, resId);
这行代码来看,没有任何对象对path进行赋值,此时path和bitmap也一定是null,编译器此时做的,仅仅是为callback分配内存空间。那到底是何时赋的值?是在后面回调该方法时赋的。继续往下看。在loadImageAsyn()方法里,由于缓存中不存在该图片,故我们新建一个下载任务并添至taskQueue中,此时又出现Synchronized,意思是当前线程(mainThread)获得runnable的monitor,并执行了runnable.notify(),然后交出runnable的monitor,终于让Thread01等到了这一刻!此时Thread01接管runnable,重新开始run(),在Thread01里开始根据task.path来下载图片,并将图片的软引用置于缓存中(对于图片这种很耗内存空间的数据格式,用软引用是好选择,因为可以在内存不足时清空缓存,避免OutOfMemory Exception),然后将图片显示在UI界面,这需要handler去处理,在handleMessage()方法里,‘何时对path进行赋值,如何赋值?’的问题终于解决,是在主线程里由task去进行赋值,而且这个赋值操作,从整个流程的时间上来看,相当靠后,这也是‘回调方法’的意思:没调用时,不必关心它的形参;需要调用时,再对其形参赋值。

零碎几点作为补充:

1.锁是相对于对象而言的,一个线程获得一个对象的锁的前提,是该线程先获得该对象的monitor,如果是从别的线程A那里得到的,需要A先释放该对象的monitor;

2.一个对象object_01,可以有多个Synchronized块,当一个线程访问objec_01t的一个synchronized(this)同步代码块时,其他线程对objec_01t中所有其它synchronized(this)同步代码块的访问将被阻塞,但对非Synchronized块的访问将不受影响;

3.调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {…} 代码段内;

4.当线程Thread_01调用obj.notify或notifyAll的时候,Thread_01正持有obj的锁,因此,等待池里的A1,A2,A3之一虽被唤醒,但是仍无法获得obj锁。直到Thread_01退出synchronized块,释放obj的锁后,A1,A2,A3中的一个才有机会获得锁继续执行。

暂时想到的就这么多,以后有新的理解再来补充。



原创粉丝点击