Galley2中线程池的应用

来源:互联网 发布:格罗姆地狱咆哮知乎 编辑:程序博客网 时间:2024/06/10 16:17

Gallery2是原生android中常用的一个应用,在对图像的批量处理、获取图片缩略图等场景时运用最多的就是线程池了,线程池的代码主要放在了com.android.gallery3d.util包下面,几个文件如下:

Future.java

FutureListener.java

PriorityThreadFactory.java

ThreadPool.java

从Galelry2中线程池的初始化说起:GalleryAppImpl.java(该类继承自Application)中:

    @Override    public synchronized ThreadPool getThreadPool() {        if (mThreadPool == null) {            mThreadPool = new ThreadPool();        }        return mThreadPool;    }
可以看到通过该单例方法获取一个ThreadPool对象并返回,那么该线程池具体是如何初始化的呢?继续走到ThreadPool.java中来看......

    public ThreadPool(int initPoolSize, int maxPoolSize) {        mExecutor = new ThreadPoolExecutor(                initPoolSize, maxPoolSize, KEEP_ALIVE_TIME,                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),                new PriorityThreadFactory("thread-pool",                android.os.Process.THREAD_PRIORITY_BACKGROUND));    }
从该方法可以看到,里面只是new ThreadPoolExecutor。ThreadPoolExecutor是在java.util.concurrent包下面的(处理java并发),其中参数较多,建议先了解一下该部分基本知识,链接:http://www.infoq.com/cn/articles/java-threadPool/ ,如果文章看完后,应该对其中的每个参数都有所了解。

值得注意的是线程池中线程的创建是通过PriorityThreadFactory.java,可以看到创建的线程命名都是以"thread-pool-"  + 数字:

    public PriorityThreadFactory(String name, int priority) {        mName = name;        mPriority = priority;    }    @Override    public Thread newThread(Runnable r) {        return new Thread(r, mName + '-' + mNumber.getAndIncrement()) {            @Override            public void run() {                Process.setThreadPriority(mPriority);                super.run();            }        };    }
在做平台开发时,经常会碰到ANR的问题,一般我们通过trace分析各个线程的堆栈信息。如果怀疑是线程池这块出了问题,就可以直接搜索"thread-pool-",来进行定位。

分析完了线程池的初始化,下面就是如何向线程池中提交任务?从ThradPool.java中:

    public <T> Future<T> submit(Job<T> job, FutureListener<T> listener) {        Worker<T> w = new Worker<T>(job, listener);        mExecutor.execute(w);        return w;    }    public <T> Future<T> submit(Job<T> job) {        return submit(job, null);    }
从上面可以看出,如果需要提交任务到线程池,则需要实现Job接口,然后在生成一个Worker对象,一点一点来,先看Job接口都有什么:

    // A Job is like a Callable, but it has an addition JobContext parameter.    public interface Job<T> {        public T run(JobContext jc);    }
该接口很简单,里面就是一个run方法(是不是很像runnable接口),这里面就是线程真正执行的地方,如果要了解线程怎么执行其run方法,那么还需要去看看Worker这个类,该类涉及内容较多:

private class Worker<T> implements Runnable, Future<T>, JobContext {

该类实现了runnable接口,以及Future和JobContext接口(这两个是什么先不用管),我们先把大致的过程梳理一下:

1、从上面的submit方法中可以看出,真正提交到线程池的是Worker对象,该对象实现了runnable接口,那么当线程池轮到该线程执行时,势必会执行其run方法;

2、我们提交的实现Job接口中的run方法在什么时候执行呢?仔细想想就会明白,它肯定是在Worker对象的run方法中执行的,不信?那么来验证一下:

    // This is called by a thread in the thread pool.    @Override    public void run() {        T result = null;        // A job is in CPU mode by default. setMode returns false        // if the job is cancelled.        if (setMode(MODE_CPU)) {            try {                result = mJob.run(this);            } catch (Throwable ex) {                Log.w(TAG, "Exception in running a job", ex);            }        }        synchronized(this) {            setMode(MODE_NONE);            mResult = result;            mIsDone = true;            notifyAll();        }        if (mListener != null) mListener.onFutureDone(this);    }

从上面代码可以很清楚的看到try catch语句中的mJob.run(this),果然和我们猜测的一样。继续看代码会发现要想执行这个run方法必须setMode这个方法返回true,这个方法究竟是干什么?猜不到,继续向下分析吧......

        @Override        public boolean setMode(int mode) {            // Release old resource            ResourceCounter rc = modeToCounter(mMode);            if (rc != null) releaseResource(rc);            mMode = MODE_NONE;            // Acquire new resource            rc = modeToCounter(mode);            if (rc != null) {                if (!acquireResource(rc)) {                    return false;                }                mMode = mode;            }            return true;        }        private ResourceCounter modeToCounter(int mode) {            if (mode == MODE_CPU) {                return mCpuCounter;            } else if (mode == MODE_NETWORK) {                return mNetworkCounter;            } else {                return null;            }        }

注意其中mMode默认为MODE_NONE,setMode默认传入的参数为MODE_CPU:

    ResourceCounter mCpuCounter = new ResourceCounter(2);    private static class ResourceCounter {        public int value;        public ResourceCounter(int v) {            value = v;        }    }

这里要注意mCpuCounter是ThreadPool的属性,是线程间共享的。现在需要搞清楚ResourceCounter的作用,从上面的setMode方法内的第11行看起,看它具体的内容:

        private boolean acquireResource(ResourceCounter counter) {            while (true) {                synchronized (this) {                    if (mIsCancelled) {                        mWaitOnResource = null;                        return false;                    }                    mWaitOnResource = counter;                }                synchronized (counter) {                    if (counter.value > 0) {                        counter.value--;                        break;                    } else {                        try {                            counter.wait();                        } catch (InterruptedException ex) {                            // ignore.                        }                    }                }            }            synchronized (this) {                mWaitOnResource = null;            }            return true;        }
这里进入之后是个死循环,首先判断是否有取消的操作,如果有的话直接返回false。否则,判断counter.value的大小,从上面的setMode中可知,默认MODE_CPU情况下couter.value的值为2。所以首先会将counter.value减1,然后break跳出循环,从而返回true,最终Job的run方法得到执行。

继续分析会发现当线程池中

第二个线程运行到这里会使counter.value变为0,

第三个线程运行到这里时会走到else里面,导致counter进入wait状态,这里要注意处于wait状态是会释放同步锁的。

第四个线程运行到这里同样会处于wait状态。

处于wait状态需要couter.notify()或者notifyAll()方法来唤醒。主要有两个地方来唤醒,第一个:

        @Override        public synchronized void cancel() {            if (mIsCancelled) return;            mIsCancelled = true;            if (mWaitOnResource != null) {                synchronized (mWaitOnResource) {                    mWaitOnResource.notifyAll();                }            }            if (mCancelListener != null) {                mCancelListener.onCancel();            }        }

可以看出如果用户调用了cancel方法,则会调用mWaitOnResource.notifyAll()方法,会唤醒。唤醒后重新执行while循环内,发现最终会返回false,也就意味着取消该任务,其提交的job中的run方法将不会执行。

第二个唤醒的地方:

        private void releaseResource(ResourceCounter counter) {            synchronized (counter) {                counter.value++;                counter.notifyAll();            }        }

该方法,是在执行中执行setMode方法(第二次进入,此时mMode为MODE_CPU,也即是Worker中run方法的第17行)会将counter.value加1。并且唤醒其它处于wait状态的线程,从而使别的线程能够获取资源,从而得到执行。

整体运行的思路已经走完了,总结一下:

1、线程池中有4个线程,而真正同时并行运行job的run()方法的线程只有2个,主要是通过ResourceCounter来控制(可以通过修改ResourceCounter的value属性来修改);

2、上面还有一些内容没有涉及到,就是FutureListenser、CancelListener等接口,这些从接口的名字就可以知道FutureListener是在job中的run方法运行完后会回调其中的方法,CancelListener是job被取消后会回调其中的方法。只要上面的整个流程清晰后,这些内容都可以结合源代码很容易的理解;

3、调试时可以将断点打在Worker中的run方法中,只要线程池执行到的时候肯定会执行其run方法。

0 0
原创粉丝点击