Java线程池总结和常用开源库的使用

3,407 阅读5分钟

new Thread的弊端

执行一个异步任务你还只是如下new Thread吗?

new Thread(new Runnable() {

	@Override
	public void run() {
		// TODO Auto-generated method stub
	}
}).start();

new Thread的弊端如下:

  • 每次new Thread新建对象性能差。
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
  • 缺乏更多功能,如定时执行、定期执行、线程中断。 相比new Thread,Java提供的四种线程池的好处在于:
  • 重用存在的线程,减少对象创建、消亡的开销,性能佳。
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  • 提供定时执行、定期执行、单线程、并发数控制等功能。

Java 线程池

Java通过Executors提供四种线程池,分别为:

  • new CachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • new FixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • new ScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  • new SingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

Executors 主要是这个构造函数:

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

而Executors工厂类一共可以创建四种类型的线程池,通过Executors.newXXX即可创建。下面就分别都介绍一下把。

1. FixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
	final int index = i;
	fixedThreadPool.execute(new Runnable() {
 
		@Override
		public void run() {
			try {
				System.out.println(index);
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	});
}

因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。

定长线程池的大小最好根据系统资源进行设置。

原理

public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
  • 它是一种固定大小的线程池;
  • corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
  • keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效;
  • 阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;
  • 由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;- 由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。

2. CachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
	final int index = i;
	try {
		Thread.sleep(index * 1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
 
	cachedThreadPool.execute(new Runnable() {
 
		@Override
		public void run() {
			System.out.println(index);
		}
	});
}
原理

//构造函数
public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
}

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

  • 它是一个可以无限扩大的线程池;
  • 它比较适合处理执行时间比较小的任务;corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
  • keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
  • 采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。

3. SingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
	final int index = i;
	singleThreadExecutor.execute(new Runnable() {

		@Override
		public void run() {
			try {
				System.out.println(index);
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	});
}

结果依次输出,相当于顺序执行各个任务。

原理

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

它只会创建一条工作线程处理任务;采用的阻塞队列为LinkedBlockingQueue;

4. ScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
 
	@Override
	public void run() {
		System.out.println("delay 3 seconds");
	}
}, 3, TimeUnit.SECONDS);

表示延迟3秒执行。

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

	@Override
	public void run() {
		System.out.println("delay 1 seconds, and excute every 3 seconds");
	}
}, 1, 3, TimeUnit.SECONDS);

表示延迟1秒后每3秒执行一次。

ScheduledExecutorService比Timer更安全,功能更强大

原理

  • 它接收SchduledFutureTask类型的任务,有两种提交任务的方式:scheduledAtFixedRate, scheduledWithFixedDelay
  • 它采用DelayQueue存储等待的任务

DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若time相同则根据sequenceNumber排序;

DelayQueue也是一个无界队列;

工作线程会从DelayQueue取已经到期的任务去执行;

执行结束后重新设置任务的到期时间,再次放回DelayQueue

最佳实践:

AsynTask 4.0

  • 默认线程池是 SingleThreadExecutor; 单一串行线程池。
  • 内部还内置一个CPU*2+1的 FixedThreadPool,命名为:THREAD_POOL_EXECUTOR。

RxJava

  • Schedulers.io( )

默认是一个CachedThreadScheduler,用于IO密集型任务,如异步阻塞IO操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。

  • Schedulers.computation( )

默认是:FixedThreadPool。用于CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算,事件循环或和回调处理

  • Schedulers.immediate( )

在当前线程立即开始执行任务,相当于不指定线程。这是默认的 Scheduler

  • AndroidSchedulers.mainThread()

Android专用的,指定的操作将在 Android 主线程运行

  • Schedulers.from(executor)

使用指定的Executor作为调度器

  • Schedulers.newThread()

总是启用新线程,并在新线程执行操作。

EventBus:

  • 使用 newCachedThreadPool,实现 前台线程通知

OKHTTP:

  • 类似 newCachedThreadPool 实现:
executor = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));

Fresco

  • Default implementation of SerialExecutorService

参考

  1. 代码例子参考网络
  2. 图片流程图部分来自知乎截图