该文章是一个系列文章,是本人在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至关重要.
下图是线程池的原理模型.

Android 并发工具类与线程池-LMLPHP

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

ThreadPoolExecutor

Android 并发工具类与线程池-LMLPHP

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面试中基本必问的只是点线程池.关于并发编程的记录暂告一段落.后面可能会出番外篇.最近也入职了新公司,忙着适应新公司的时候好像怠慢了自己的积累.后面尽量会按照一个月一篇的速度更新博客,感谢关注我的粉丝.


此致,敬礼

04-24 14:57