线程池的简单使用

需求:

最近有个需求,要求把网页中的图片地址和音频地址抓出来,然后下载保存到本地。这里一个网页中有可能有一个url地址,也有可能有多个个url地址。当然,下载这种操作,另外下载超过1个,想到的就是用线程池。

实现:

第一步:先写一个ThreadPoolProxyFactory ,里面提供下载的线程池。代码如下:

/**
 * @author weijunqiang
 * @date 2018/7/20
 * 一个ThreadPoolProxyFactory ,里面提供下载的线程池
 */
public class ThreadPoolProxyFactory {
    static ThreadPoolProxy mDownLoadThreadPoolProxy;

    /**
     * 得到下载线程池代理对象mDownLoadThreadPoolProxy
     */
    public static ThreadPoolProxy getDownLoadThreadPoolProxy() {
        if (mDownLoadThreadPoolProxy == null) {
            synchronized (ThreadPoolProxyFactory.class) {
                if (mDownLoadThreadPoolProxy == null) {
                    mDownLoadThreadPoolProxy = new ThreadPoolProxy(3, 20);
                }
            }
        }
        return mDownLoadThreadPoolProxy;
    }
}

第二步:写一个线程池代理,替线程池完成一些操作。提供了三种方法:执行任务,提交任务,移除任务。

/**
 * @author weijunqiang
 * @date 2018/7/20
 * 线程池代理,替线程池完成一些操作。提供了三种方法:执行任务,提交任务,移除任务。
 */
public class ThreadPoolProxy {
    ThreadPoolExecutor mExecutor;
    private int mCorePoolSize;
    private int mMaximumPoolSize;

    /**
     * @param corePoolSize    核心池的大小
     * @param maximumPoolSize 最大线程数
     */
    public ThreadPoolProxy(int corePoolSize, int maximumPoolSize) {
        mCorePoolSize = corePoolSize;
        mMaximumPoolSize = maximumPoolSize;
    }

    /**
     * 初始化ThreadPoolExecutor
     * 双重检查加锁,只有在第一次实例化的时候才启用同步机制,提高了性能
     */
    private void initThreadPoolExecutor() {
        if (mExecutor == null || mExecutor.isShutdown() || mExecutor.isTerminated()) {
            synchronized (ThreadPoolProxy.class) {
                if (mExecutor == null || mExecutor.isShutdown() || mExecutor.isTerminated()) {
                    long keepAliveTime = 3000;
                    TimeUnit unit = TimeUnit.MILLISECONDS;
                    BlockingQueue workQueue = new LinkedBlockingDeque<>();
                    ThreadFactory threadFactory = Executors.defaultThreadFactory();
                    RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();

                    mExecutor = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, keepAliveTime, unit, workQueue,
                            threadFactory, handler);
                }
            }
        }
    }
    /**
     执行任务和提交任务的区别?
     1.有无返回值
     execute->没有返回值

     submit-->有返回值
     2.Future的具体作用?
     1.有方法可以接收一个任务执行完成之后的结果,其实就是get方法,get方法是一个阻塞方法
     2.get方法的签名抛出了异常===>可以处理任务执行过程中可能遇到的异常
     */
    /**
     * 执行任务
     */
    public void execute(Runnable task) {
        initThreadPoolExecutor();
        mExecutor.execute(task);
    }

    /**
     * 提交任务
     * Runnable
     */
    public Future submit(Runnable task) {
        initThreadPoolExecutor();
        return mExecutor.submit(task);
    }

    /**
     * 提交任务
     * Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值
     * Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。
     */
    public Future<ImgCacheBean> submit(Callable<ImgCacheBean> callable) {
        initThreadPoolExecutor();
        return mExecutor.submit(callable);
    }

    /**
     * 移除任务
     */
    public void remove(Runnable task) {
        initThreadPoolExecutor();
        mExecutor.remove(task);
    }
}

第三步:线程池的使用

    private void startDownloads(final List<ImgCacheBean> urls) {
        try {
            final List<Future<ImgCacheBean>> result = new ArrayList<>();
            for (int i = 0; i < urls.size(); i++) {
                String cacheUrl = "";
                String fileName = "";
                //TODO 将url中的汉字进行转码,空格换成"%20"
                String gradeChineseStr = RegularUtils.getChinese(urls.get(i).getHttpUrl());
                cacheUrl = decodeSrc.replace(" ", "%20");
                URL url = new URL(cacheUrl);
                Future<ImgCacheBean> future = ThreadPoolProxyFactory.getDownLoadThreadPoolProxy().submit(new DoCallable(url, urls.get(i), fileName));
                result.add(future);
            }
            //TODO 这里面使用又起一个线程,是因为Future的get()方法是一个阻塞方法,不能在主线程直接使用
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    try {
                        for (int k = 0; k < result.size(); k++) {
                            //TODO get(6, TimeUnit.SECONDS)单个文件最长等6秒
                            ImgCacheBean imgCacheBean = result.get(k).get(6, TimeUnit.SECONDS);
                            if (imgCacheBean != null) {
                                downloads.add(imgCacheBean);
                            }
                        }
                        if (downloads.size() >= urls.size()) {
                            Message msg = mHandle.obtainMessage();
                            msg.what = 1;
                            mHandle.sendMessage(msg);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        Message msg = mHandle.obtainMessage();
                        msg.what = 2;
                        mHandle.sendMessage(msg);
                    }

                }
            }.start();
        } catch (Exception e) {
            e.printStackTrace();
            initWebview();
        }
    }

总结

上面第三个使用方法,只是是部分代码,在使用的时候需要注意,在线程池调用了submit(Callable<ImgCacheBean> callable) 之后,该方法会立刻返回一个Future<ImgCacheBean> future 对象。但它并不代表Callable代表的任务已经执行完了。Callable代表的任务执行没执行完是不知道的。具体体现在Future的get()方法是一个阻塞方法。当调用Future的get()方法后,直到Callable的call方法执行完后或者抛出异常才返回结果(这个过程可能很长时间,所以使用了get(6, TimeUnit.SECONDS)方法,表示最多等6秒)。所以上述在获取future对象返回的结果时,将它另起线程处理,等有了结果再通知主线程结果。

猜你喜欢

转载自blog.csdn.net/u014434080/article/details/81175008