Android-并发工具类与线程池

11次阅读

共计 4029 个字符,预计需要花费 11 分钟才能阅读完成。

该文章是一个系列文章,是本人在 Android 开发的漫漫长途上的一点感想和记录,如果能给各位看官带来一丝启发或者帮助,那真是极好的。


前言

上一篇说到了 Android 并发编程中的 原子类与并发容器, 那么本篇呢, 继续上一篇记录一下 Android 并发编程中常用的一些工具类, 以及面试必问知识点 – 线程池.

并发工具类

CountDownLatch(等待多线程完成)

CountDownLatch 允许一个或多个线程等待其他线程完成操作。

当我们需要用多个线程分解一些比较复杂任务时, 这些任务通常符合下面两个规则:

  1. 任务可以分解为多个相互独立的子任务
  2. 所有子任务的综合结果即为该任务的结果

用法:

public class CountDownLatchTest {static CountDownLatch c = new CountDownLatch(2);
    public static void main(String[] args) throws InterruptedException {new Thread(new Runnable() {
            @Override
            public void run() {System.out.println(1);
                c.countDown();
                System.out.println(2);
                c.countDown();}
        }).start();
        c.await();
        System.out.println("3");
    }
}

CountDownLatch 的构造函数接收一个 int 类型的参数作为计数器, 如果你想等待 N 个点完成, 这里就传入 N。当我们调用 CountDownLatch 的 countDown 方法时,N 就会减 1 ,CountDownLatch 的 await 方法会阻塞当前线程, 直到 N 变成零。

除了 await()方法之外, 也可以使用另外一个带指定时间的 await 方法——await(long time,TimeUnit unit), 这个方法等待特定时间后, 就会不再阻塞当前线程。

总结来说就是等待多个线程的完成, 然后自己 (调用 await 方法的线程) 才运行.

CyclicBarrier(同步屏障)

同步屏障要做的事情是, 让一个线程到达一个屏障 (也可以叫同步点) 时被阻塞, 直到最后一个线程到达屏障时, 屏障才会开门, 所有被屏障拦截的线程才会继续运行。
用法

public class CyclicBarrierTest {static CyclicBarrier c = new CyclicBarrier(2);
    public static void main(String[] args) {new Thread(new Runnable() {
            @Override
            public void run() {
            try {c.await();
            } catch (Exception e) { }
            System.out.println(1);
            }
        }).start();
        try {c.await();
        } catch (Exception e) { }
        System.out.println(2);
    }
}

CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties), 其参数表示屏障拦截的线程数
量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障, 然后当前线程被阻塞。当所有线程都到达了屏障时 (即都调用了 await 方法) 时, 所有线程进行 CPU 竞争, 由 CPU 调度运行.

Semaphore(控制线程数量)

Semaphore, 信号量(令牌数). 信号量这个概念跟我们到一个非常火的饭店排队领号吃饭一样, 由于饭店的容量是有限的, 只能容纳 N 个人, 前面 N 个人被叫到号进去用餐, 其他的人只能等待, 直到饭店中有人离开, 才会继续叫号.

Semaphore 的构造方法 Semaphore(int permits)接受一个整型的数字, 表示可用的许可证数量。在上面的例子中这个数字就是饭店中能容纳的人数. 正在饭店内用餐的人代表正在运行的线程, 在外面的等待的人代表阻塞的线程, 吃完饭离开的人代表已经运行完毕的线程.

用法

public class SemaphoreTest {
    private static final int THREAD_COUNT = 30;
    private static ExecutorServicethreadPool = Executors
    .newFixedThreadPool(THREAD_COUNT);
    private static Semaphore s = new Semaphore(10);
        public static void main(String[] args) {for (inti = 0; i< THREAD_COUNT; i++) {threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {s.acquire();
                        System.out.println("save data");
                        s.release();} catch (InterruptedException e) {}}
            });
        }
        threadPool.shutdown();}
}

在代码中, 虽然有 30 个线程在执行, 但是只允许 10 个并发执行。Semaphore(10)表示允许 10 个线程获取许可证, 也就是最大并发数是 10。
Semaphore 使用 Semaphore 的 acquire() 方法获取一个许可证 , 使用完之后调用release() 方法归还许可证 。还可以用 tryAcquire() 方法尝试获取许可证。

线程池

Java 中的线程池是运用场景最多的并发框架, 几乎所有需要异步或并发执行任务的程序
都可以使用线程池。在开发过程中, 合理地使用线程池能够带来 3 个好处。

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时, 任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源, 如果无限制地创建, 不仅会消耗系统资源, 还会降低系统的稳定性, 使用线程池可以进行统一分配、调优和监控。但是, 要做到合理利用线程池, 必须对其实现原理了如指掌。

Java 中常用的线程池都是 ThreadPoolExecutor 不同的配置产生的以符合不同的场景. 所以理解 ThreadPoolExecutor 至关重要.
下图是线程池的原理模型.

有了线程池的原理模型之后, 我们再看在 Java 库中是如何实现这个模型的, 下面我们来看 ThreadPoolExecutor

ThreadPoolExecutor

Java 的常用线程池

FixedThreadPoolExecutor

FixedThreadPool 被称为可重用固定线程数的线程池。

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

FixedThreadPoolExecutor 是一种线程数量固定的线程池, 当线程处于空闲状态时, 它们并不会被回收, 除非线程池被关闭了. 当所有的线程都处于活动状态时, 新任务都会处于等待状态, 直到有线程空闲出来. 由于 FixedThreadPool 只有核心线程并且这些核心线程不会被回收, 这意味着它能够更加快速的响应外界的请求.

SingleThreadExecutor 详解

SingleThreadExecutor 是使用单个 worker 线程的 Executor.

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

SingleThreadPool 内部只有一个核心线程, 它确保所有的任务都在同一个线程中按顺序执行.

CachedThreadPool

CachedThreadPool 是一个会根据需要创建新线程的线程池。

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

CachedThreadPool 是一种线程数量不定的线程池, 它只有非核心线程, 并且其最大线程数 Integer.MAX_VALUE 是一个很大的数, 实际上就相当于最大线程数可以任意大. 当线程池中的线程都处于活动状态时, 线程池会创建新的线程来处理新任务, 否则就会利用空闲的线程来处理新任务. 线程池中的空闲线程都有超时机制,60 秒, 超过 60 秒的的闲置线程就会被回收.
CachedThreadPoll 比较适合执行大量的耗时较少的任务

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor。它的核心线程数量是固定的, 而非核心线程数是没有限制的, 并且当非核心线程闲置时会被立即回收. 它主要用来在给定的延迟之后运行任务, 或者定期执行任务。


本篇总结

本篇呢, 记录了一下并发编程中常用的一些工具类以及 java 或者 Android 面试中基本必问的只是点线程池. 关于并发编程的记录暂告一段落. 后面可能会出番外篇. 最近也入职了新公司, 忙着适应新公司的时候好像怠慢了自己的积累. 后面尽量会按照一个月一篇的速度更新博客, 感谢关注我的粉丝.


此致,敬礼

正文完
 0