Android 线程池

  1. 线程池的种类⭐⭐⭐⭐⭐

  1. 线程池的优点⭐⭐⭐⭐

  1. 平时当中使用案例⭐⭐⭐⭐

  1. ThreadPoolExecutor类有了解吗?⭐⭐(需掌握,但一般不会提问)

目录

  • 1、为何要使用线程池,有何优点

  • 2、ThreadPoolExecutor

  • 2.1 构造函数

  • 2.2 线程池工作原理

  • 3、线程池的种类和特点

  • 3.1 FixThreadPool

  • 3.2 SingleThreadPool

  • 3.3 CachedThreadPool

  • 3.4 ScheduledThreadPool

  • 总结

  • 4、实际使用案例

1、为何要使用线程池,有何优点

平时的安卓开发中,很多耗时操作不能在主线程中执行,不然就会阻塞主线程,因此我们常见的做法是new 一个Thread来执行耗时操作,最后通过Handler切换到主线程来修改UI。然而当线程数量多的时候,有可能会导致死机和OOM。在安卓中,我们可以使用线程池来管理我们所创建的线程。所谓线程池就是事先创建一系列线程,把它们放在一个容器里,使用的时候直接从池子里拿线程,而不需要重新去new一个。我们先讨论线程池的优点:

  • 多个线程可以多次复用,避免因线程频繁创建和销毁给系统带来的损耗,同时也可以提高系统响应速度,当任务需要执行时不需要创建新的线程就可以执行;

  • 线程池的最大并发数可控制,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象;

  • 能够对线程进行管理,并提供定时执行以及定间隔循环执行等功能;

2、ThreadPoolExecutor

2.1 构造函数

待会会讲的4种线程池都是通过ThreadPoolExecutor来创建的,因此需要先了解ThreadPoolExecutor:

// 构造函数
 public ThreadPoolExecutor(int corePoolSize,
 	int maximumPoolSize,
 	long keepAliveTime,
 	TimeUnit unit,
 	BlockingQueue<Runnable> workQueue,
 	ThreadFactory threadFactory,
 	RejectedExecutionHandler handler) 
        
// 使用demo
    // 创建线程池
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1000, TimeUnit.SECONDS, new LinkedBlockingQueue<>()), new ThreadPoolExecutor.AbortPolicy());
    // 通过循环向线程池中添加10个任务
    for (int i = 0; i < 10; i++) {
        executor.execute(() -> {
            // do something
        });
    }
    // 关闭线程池
    executor.shutdown();

先看看构造函数的各个参数:

  • corePoolSize:线程池的核心线程数量。核心线程当该线程处于闲置时间也不会回收的线程。当然,我们可以执行threadPoolExecutor.allowCoreThreadTimeOut(true)后,当闲置的核心线程的等待新任务时间超过了keepAliveTime也会被终止;

  • maximumPoolSize:线程池最大线程数,等于核心线程 + 非核心线程。如果运行的线程总数量超过这个数值,那么接下里新来的任务都会阻塞等待;

  • keepAliveTime:闲置的非核心线程等待时间超过keepAliveTime设定的时间就会被回收,而核心线程需要执行threadPoolExecutor.allowCoreThreadTimeOut(true)后超过时间才会被回收;

  • unit:keepAliveTime的单位:

单位

含义

TimeUnit.DAYS

TimeUnit.HOURS

TimeUnit.MINUTES

TimeUnit.MILLISECONDS

毫秒

TimeUnit.MICROSECONDS

微秒

TimeUnit.NANOSECONDS

纳秒

  • workQueue:这就是我们执行execute()添加的任务所存放的工作阻塞队列,有以下选项: 阻塞队列 | 说明 | | ----- | ----- | | ArrayBlockingQueue | 基于数组实现的有界的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。 | | LinkedBlockingQueue | 基于链表实现的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。 | | SynchronousQueue | 内部没有任何容量的阻塞队列。在它内部没有任何的缓存空间。对于SynchronousQueue中的数据元素只有当我们试着取走的时候才可能存在。 | | PriorityBlockingQueue | 具有优先级的无限阻塞队列。|

  • threadFactory:从名字直接翻译就知道是“线程工厂”,用于新线程的创建,默认为Executors.defaultThreadFactory();

  • handler:可以看到是RejectedExecutionHandler对象,该对象只是一个接口类,里面只有一个rejectedExecution方法。如果当前的活动线程达到maximumPoolSize或者执行任务失败了,就会执行rejectedExecution()。在ThreadPoolExecutor有四个内部类实现了rejectedExecution()方法,代表四种不同的处理方式:

可选值

说明

CallerRunsPolicy

只用调用者所在线程来运行任务。

AbortPolicy

直接抛出RejectedExecutionException异常。

DiscardPolicy

丢弃掉该任务,不进行处理。

DiscardOldestPolicy

丢弃队列里最近的一个任务,并执行当前任务。

2.2 线程池工作原理

线程池的工作原理涉及核心线程、非核心线程、阻塞队列等,当有新的任务加入线程池时,池内工作原理具体如下:

  1. 当正在运行的线程数 < 核心线程数,马上创建核心线程处理这个任务;

  1. 当正在运行的线程数 >= 核心线程数,把该任务加入阻塞队列;

  1. 当阻塞队列满了且正在运行的线程数 < 线程池最大线程数,则创建新的非核心线程来处理该任务;

  1. 当阻塞队列满了且正在运行的线程数 >= 线程池最大线程数,则线程池调用handler的reject方法拒绝本次新任务添加。

3、线程池的种类和特点

Android中的线程池都是直接或间接通过配置ThreadPoolExecutor来实现不同特性的线程池。在Android中最常见的4种线程池分别为FixThreadPool、CachedhreadPool、SingleThreadPool、ScheduleThreadExecutr.

3.1 FixThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads,
		0L, TimeUnit.MILLISECONDS,
		new LinkedBlockingQueue<Runnable>());
}
  • 线程数量固定的线程池,只有核心线程(因为corePoolSize = maximumPoolSize),如果线程池的线程处于空闲状态的话,这些核心线程也不会被回收,所以FixThreadPool也可以更快速地响应外界请求;

  • 如果所有线程都在忙碌状态,如果有新的任务到来就会处于等待状态,而不会创建新的线程来执行;

  • 适用于很稳定、很正规的并发线程,多用于服务器;

3.2 SingleThreadPool

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

单例线程池,任意时间内池中只有一个线程。因此,当有一个线程正在执行时,其他的任务都需要在任务队列里等待。

3.3 CachedThreadPool

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

无界可自动回收线程池,其中无界体现在maximumPoolSize设置为Integer.MAX_VALUE,Integer.MAX_VALUE代表很大的数,因此可以理解为无界限。可回收是因为核心线程数设置为0,超时时间设置为60秒,因此该线程池里所有的线程一旦处于闲置时间超过60秒就会被自动回收了。因此这种线程池有以下特点:

  1. 任何任务立即被执行;

  1. 闲置时不占系统资源;

  1. 适合执行大量的耗时较少的任务;

3.4 ScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

周期任务线程池,核心线程数固定,非核心线程则没有限制数量,非核心线程闲置时立刻回收。适合执行定时任务以及有固定周期的重复任务。

总结:

类型

创建方法

说明

FixedThreadPool

Executors.newFixedThreadPool(int nThreads)

线程数固定的线程池,只有核心线程,并且不会被回收,没有超时机制

CachedThreadPool

Executors.newCachedThreadPool()

无界可自动回收线程池,只有非核心线程,闲置线程超过60秒自动回收

ScheduledThreadPool

Executors.newScheduledThreadPool(int corePoolSize)

周期任务线程池,核心线程数固定,非核心线程数无限制,非核心线程闲置时立刻回收

SingleThreadExecutor

Executors.newSingleThreadExecutor()

单例线程池,确保所有任务在同一线程中按顺序执行

4、实际使用案例

面试官问完理论题目后,还会问实际开发中使用线程池的实际案例。以下给一个之前在做EMMC测试时需要写文件的实际demo:

mXRExecutorService = Executors.newFixedThreadPool(THREAD_NUMBS);
private void writeFileWithMultiThread(final File file, int size, byte[] buff){
    if (file == null) {
        return;
    }
    // CountDownLatch允许一个或者多个线程去等待其他线程完成操作
    CountDownLatch connectedSignal = new CountDownLatch(THREAD_NUMBS);

    long sizeOfPerThread = size / THREAD_NUMBS;
    for (int i = 0; i < THREAD_NUMBS; ++i) {
        byte[] perByte = Arrays.copyOfRange(buff, (int)(i * sizeOfPerThread), (int)((i + 1) * sizeOfPerThread));
        writeFile(file, connectedSignal, perByte, (int)(i * sizeOfPerThread));
    }

    waitForLatch(connectedSignal); // 自定义函数,实际调用await()
}

private void writeFile(final File file, final CountDownLatch connectedSignal, byte[] byteValue, int startIndex) {
    mXRExecutorService.execute(() -> {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(file, "rwd");
            raf.seek(startIndex);
            raf.write(byteValue);
            raf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        LogUtils.d("write index=" + startIndex + " over");
        connectedSignal.countDown();
    });
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值