Android 并发二三事之Java线程池

来源:互联网 发布:淘宝网首页中年女装 编辑:程序博客网 时间:2024/05/19 12:17

最近在项目中接触到了很多有关于多线程方面的东西,并且刚好前段时间看了Java并发编程实战那本说,
所以想将自己所了解到的,以及实际中碰到的问题总结一下。

打算首先介绍一下,Java多线程相关的基础,例如Thread,Runnable。虽然这个极其的基础,但是我觉得任何东西都
绕不过基础的知识。重点会在介绍线程池,包括线程池相关类的层级结构、相关参数等。
以及在Android中有那些多线程表现形式,例如:AsyncTask,HandlerThread,IntentService, AsycTaskLoader等。
之后希望能简单的表述一下多线程与Future, TimeUnit , CountDownLatch 等等相关类的关系,以及如何在子线程中如何开启子线程去请求数据,子线程与主线程如何交互数据等。
还有就是介绍多线程相关概念,锁, 可重入锁, 死锁, 如何排查死锁, 线程发布, 竞态条件, 数据竞争 等等

应该会分几篇博客来总结相关的知识。当然其中肯定会有一些错误之处,欢迎留言指出。我会及时更正。

以上相关所有知识点,有许多借鉴自其他的大神的博客,由于看了许多,没办法一一列举,在此表示感谢。许多概念相关的知识来自于《Java并发编程实战》那本书。

一、Thread 继承Thread类,重写run方法。

我觉得Thread应该是被最经常用到的。当只需要一个简单的线程,不需要管理线程时直接:

new Thread(){            @Override            public void run() {        //请求网络等            }    }.start();

一个匿名内部类就写完了,相信很多人都写过这样的代码。需要注意的是,如果在Activity中写这样的代码很容易造成内存泄露。
即当Activity结束时,线程的工作还没有做完,这就会导致该Activity不会被回收。可以考虑用静态内部类的形式代替上面的写法。

二、 Runnable 实现Runnable接口重写run()方法

public class DisableRunnable implements Runnable{        @Override        public void run() {            //请求网络等        }    }

Runnable 是执行任务的单元,需要用Thread包装一下才可以执行。
Runnable 接口在Android中的使用也非常的多.例如View.post(),Handler.post()等等。

每当我们看到这样的代码时:

new Thread(){runnable}.start();

当你考虑用更灵活的策略来执行任务时,可以考虑利用线程池代替Thread。

三、 线程池:

下面主要说一下线程池相关的总结,
线程池结构:

最顶层接口 Executor

public interface Executor {    void execute(Runnable command);}

ExecutorService接口 继承Executor 定义了一些有关于生命周期的方法。

public interface ExecutorService extends Executor {    void shutdown();    List<Runnable> shutdownNow();    boolean isShutdown();    boolean isTerminated();    boolean awaitTermination(long timeout, TimeUnit unit)        throws InterruptedException;    <T> Future<T> submit(Callable<T> task);    <T> Future<T> submit(Runnable task, T result);    Future<?> submit(Runnable task);    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)        throws InterruptedException;    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,                                  long timeout, TimeUnit unit)        throws InterruptedException;    <T> T invokeAny(Collection<? extends Callable<T>> tasks,                    long timeout, TimeUnit unit)        throws InterruptedException, ExecutionException, TimeoutException;}

ThreadPoolExecutor 间接实现了ExecutorService 是真正做事的线程池。最常见的几种线程池都是它的实现。

ThreadPoolExecutor 常用的构造方法:

public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              RejectedExecutionHandler handler){}

相关参数解释:
corePoolSize 核心线程池大小
maximumPoolSize 最大线程池大小
keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间
TimeUnit keepAliveTime时间单位
workQueue 阻塞任务队列
RejectedExecutionHandler 饱和策略 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理

1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程

注意:
这里maximumPoolSize 指的是corePoolSize + 由于队列已满并且maximumPoolSize>corePoolSize时,为执行任务创建的线程数。
所以第4条应该说成,当workQueue已满,并且线程池内的线程数已经达到了maximumPoolSize最大线程数。这时,再次提交任务会交给RejectedExecutionHandler 饱和策略去处理。

四、这里BlockingQueue
可以为:SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue 等等。
我们需要先了解这几个队列,当清楚这些队列的性质后,再去看那几个常用的线程池,将会非常容易理解。

SynchronousQueue
SynchronousQueue 首先它是无界的,也就是说它存储的能力是没有限制的。
但是它每一个put操作必须等待一个take操作。也就是说在某一次添加元素后,必须要等其他线程将它取走,否则不能添加。

LinkedBlockingQueue
LinkedBlockingQueue 无界队列,也就是说使用LinkedBlockingQueue时,当所有corePoolSize都在忙时,新任务都会在LinkedBlockingQueue中等待。
因为LinkedBlockingQueue是无界的(最大长度为Integer.MAX_VALUE,这里认为无界)。这个时候maximumPoolSize其实就无效了,因为只有当LinkedBlockingQueue已满时,才会创建新的线程执行任务。而这里LinkedBlockingQueue永远都不会满。所以maximumPoolSize也就无效了,也就是说利用这个队列构造的线程池,永远也不会创建新的线程。

ArrayBlockingQueue
ArrayBlockingQueue 有界队列 可以设置队列的大小,能够有效的防止资源消耗。但是者也就导致这种情况控制起来会相对复杂,JDK建议的几种常见的线程池都没有使用这个队列。

PriorityBlockingQueue 优先级队列

Executors 线程池工具类,用于生产线程池。

五、几类比较常见的线程池:

public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }

解释:构建一个固定数目的线程池。corePoolSize 与 maximumPoolSize 数值相等。当线程池中没有空闲线程时,利用 LinkedBlockingQueue
保存阻塞任务,因为之前已经说过了 LinkedBlockingQueue 是无界的,也就是说这个队列永远不会满,那maximumPoolSize 其实就没有任何意义。

public static ExecutorService newSingleThreadExecutor() {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>()));    }

解释:构建一个单线程(只有一个线程,单不一定是同一个线程)的线程池。corePoolSize 与 maximumPoolSize 均为1 .利用无界的LinkedBlockingQueue 作为保存任务的队列,这样就保证无论提交多少个人物,线程池只能有一个线程在运行,其余的任务均保存在 LinkedBlockingQueue 队列中。

public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>());    }

解释:构建一个具有缓存功能的线程池,corePoolSize = 0, maximumPoolSize = Integer.MAX_VALUE 。队列用的是SynchronousQueue。每加入一个任务,线程池便会构建一个线程将它取出执行(如果有空闲的线程,会利用空闲的线程,而不会创建新的线程),这样,加入多少个任务便会创建相同个数的线程。所以这个线程池的 maximumPoolSize 为Integer.MAX_VALUE。这就保证了线程池的最大线程数是无界的,理论上线程池可以有任意个线程。当线程执行完任务后,超过60秒的空闲时间即被回收销毁。

六、饱和策略:

RejectedExecutionHandler 参数用来表示饱和策略。即表示当有界队列已满,并且当前线程池中的线程数已达到 maximumPoolSize ,这时再提交任务,会交给RejectedExecutionHandler 来处理。

注意:这里一定是有界队列,因为无界队列我们认为是永远也无法填满的(SynchronousQueue直接由生产者提交给工作线程),那么也就永不到饱和策略了。

public interface RejectedExecutionHandler {    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);}

RejectedExecutionHandler 本身是一个接口。在Executors中提供了几个实现类。

1)AbortPolicy:中止,executor抛出未检查RejectedExecutionException,调用者捕获这个异常,然后自己编写能满足自己需求的处理代码。

2)DiscardRunsPolicy:遗弃最旧的,选择丢弃的任务,是本应接下来就执行的任务。

3)DiscardPolicy:遗弃会默认放弃最新提交的任务(这个任务不能进入队列等待执行时)

4)CallerRunsPolicy:调用者运行,既不会丢弃哪个任务,也不会抛出任何异常,把一些任务推回到调用者那里,以此减缓新任务流。它不会在池线程中执行最新提交的任务,但它会在一个调用了execute的线程中执行。

七、线程工厂:
线程池另外还有一个参数便是:ThreadFactory。之前在介绍常用的构造方法没有说它的原因是,一般用不到。其他构造方法中都给出了默认实现:DefaultThreadFactory。

public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler) {}public interface ThreadFactory {    Thread newThread(Runnable r);}

ThreadFactory 在 Executors 中也提供了几个实现类,一般博主所接触过的都是 DefaultThreadFactory 。当然我们可以自己定制,
用于添加一些Log等等。之前推荐的几种线程池都有 ThreadFactory 作为参数的方法。

八、常见的线程池的使用:

自定义线程池:

private final static ExecutorService executorService = new ThreadPoolExecutor(            5,            10,            30L,            TimeUnit.SECONDS,            new LinkedBlockingQueue<Runnable>(),            new RejectedExecutionHandler() {                @Override                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {                    //DO nothing                }            });

这里我们用的是自定义的线程池。我们定义了一个核心线程池为5的线程池,线程池内最大的线程池数为10。

当然由于我们这里定义的缓存队列为LinkedBlockingQueue, 没有制定队列大小,那么其默认无Integer.MAX_VALUE。
也就是无限大,所以最大线程数没有用处。

饱和策略也是自定的,这里当达到执行饱和策略时,什么都不做,直接丢弃。

public void request() {        executorService.execute(new Runnable() {            @Override            public void run() {                try {                    //请求网络等。                } catch (Exception e) {                    e.printStackTrace();                }            }        });    }利用Executors 提供的线程池:public void request() {        ExecutorService executor = Executors.newFixedThreadPool(5);        executor.execute(new Runnable() {            @Override            public void run() {                try {                    //请求网络等。                } catch (Exception e) {                    e.printStackTrace();                }            }        });    }

OK, 线程池相关就介绍到这里,下一篇会介绍Future,FutureTask, Callable 等等相关知识。

0 0