Java多线程 - 线程池

在咱们开发中常常会使用到多线程,好比在Android中,网络请求或一些耗时操做必须放在子线程中运行,每每会经过Thread开启一个子线程去执行耗时操做,待子线程执行完毕后再经过Handler切换到主线程中运行;若是N屡次建立子线程,就没法管理所建立的子线程,它们相互之间竞争,颇有可能因为占用过多资源而致使死机或者OOM。因此java提供了线程池来管理咱们建立的线程。html

线程池的优点:java

1. 下降资源消耗。经过重复利用已经建立的线程下降线程的建立与销毁形成的消耗。数组

2. 提升响应速度。当任务到达时,任务能够不须要等到线程建立就能当即执行。缓存

3. 提升线程的可管理性。线程是稀缺资源,若是无限制第建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一分配、调优和监控。网络

ThreadPoolExecutor

能够经过ThreadPoolExecutor来建立一个线程池,ThreadPoolExecutor类一共用4个构造函数。多线程

ExecutorService threadPool = new ThreadPoolExecutor( …… );

下面是拥有最多参数的构造函数:less

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

1. corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会建立ide

一个线程来执行任务,即便其余空闲的基本线程可以执行新任务也会建立线程,等到须要执行的任务数大于线程池基本大小时就再也不建立。若是调用了线程池的prestartAllCoreThreads方法,线程池会提早建立并启动全部基本线程。函数

2. maximumPoolSize(线程池最大数量):线程池容许建立的最大线程数。若是队列满了,而且建立的线程数小于最大线程数,则线程池会再建立新的执行任务。this

3. keepAliveTime(线程活动保持时间):线程池的工做线程空闲后,保持存活的时间。因此,若是任务不少,而且每一个任务执行的时间比较短,能够调大时间,提升线程的利用率。

4. TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒等

5. workQueue(线程池中保存等待执行任务的阻塞队列):经过线程池中的execute方法提交的Runable对象会存储在该队列中。咱们能够选择下面几个阻塞队列:

 

阻塞队列 说明
ArrayBlockingQueue 基于数组实现的有界阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序
LinkedBlockingQueue       基于链表实现的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。
SyschronousQueue 内部没有任何容量的阻塞队列。在它内部没有任何的缓存空间,对于该队列中的数据元素只有当咱们试着取走的时候才可能存在
PriorityBlockingQueue 具备优先级的无限阻塞队列

能够经过实现BlockingQueue接口来自定义所需的阻塞队列。

6. ThreadFactory(线程工厂,为线程池提供新线程的建立):能够给每一个建立出来的线程设置名字。ThreadFactory是一个接口,里面只有一个newThread方法。

7. RejectedExecutionHandler(饱和策略):RejectedExecutionHandler是一个接口,里面只有rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法。当任务队列已满而且线程池中的活动线程已经达到所限定的最大值或者没法成功执行任务,这时候ThreadPoolExecutor 会调用RejectedExecutionHandler中的rejectedExecution方法。在线程池中默认是AbortPolicy,在没法处理新任务是会抛出RejectedExecutionException异常。此外还有3中策略,分别是:

可选值 说明
CallerRunsPolicy 用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,可以减缓新任务的提交速度
DiscardPolicy 不能执行的任务,并将该任务删除
DiscardOldestPolicy 丢弃队列最近的任务,并执行当前任务

ThreadPoolExecutor的使用

1. 建立:一个拥有5个核心线程,最大能容入10个线程的线程池

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10,
				10, TimeUnit.SECONDS, new ArrayBlockingQueue(10));

2. 线程池的任务提交有两种方式:execute方法和submit方法。就是将线程放入线程池

execute方法:没有返回值,没法判断任务是否被线程池执行成功。

executor.execute(new Runnable() {			
	@Override
	public void run() {
		System.out.println("线程:"+ Thread.currentThread().getName());
					
    }
});

 

submit方法:使用submit方法提交任务,会返回一个Future(接口)对象,能够经过该对象来判断任务是否执行成功,经过还能够改对象的get方法来获取返回值。若是子线程任务没有执行完毕,会一直阻塞在get无参方法中直到执行完成。若是使用getget(long timeout, TimeUnit unit)方法则会阻塞等待所设的时间值,这时可能子线程中任务还没执行完毕。关于Future的介绍点击打开连接

 

final int result=i;
Future<Integer> future = executor.submit(new Callable<Integer>() {

	@Override
	public Integer call() throws Exception {
		System.out.println("线程:"+Thread.currentThread().getName());
			return result;
		}
				
});
						
try {
    Integer integer = future.get();
    System.out.println(integer);
} catch (InterruptedException e) {
	e.printStackTrace();
} catch (ExecutionException e) {
	e.printStackTrace();
}

关于sbumit(Callable)源码

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

 

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

从源码上可看出,把Callable实例做为参数传入,生成FutureTask对象,而后把这个对象当成一个Runnable,做为参数另起线程。
 

3. 线程池的关闭

关闭线程池提供了两个方法:shutdown()shutdownNow()

shutdown:将线程池状态设置成SHUTDOWN状态,而后中断全部没有正在执行任务的线程

shutdownNow:将线程池的状态设置成STOP状态,而后中断全部任务(包括正在执行的线程)的线程,而且返回执行任务列表。

例子:

public class ThreadPoolTest {

	public static void main(String[] args) {

		ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 10,
				TimeUnit.SECONDS, new ArrayBlockingQueue(10));

		
		for (int i = 0; i < 10; i++) {
			//提交任务方法一:execute方法
			executor.execute(new Runnable() {

				@Override
				public void run() {
					System.out
							.println("线程:" + Thread.currentThread().getName());

				}
			});

			//提交任务方法一:submit方法
			final int result = i;
			Future<Integer> future = executor.submit(new Callable<Integer>() {

				@Override
				public Integer call() throws Exception {
					System.out
							.println("线程:" + Thread.currentThread().getName());
					return result;
				}

			});

			try {
				Integer integer = future.get();
				System.out.println(integer);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}

		}
		executor.shutdown();

	}

}

execute方法的执行结果:

线程:pool-1-thread-1
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-3
线程:pool-1-thread-5
线程:pool-1-thread-4

submit方法的执行结果:

线程:pool-1-thread-1
0
线程:pool-1-thread-2
1
线程:pool-1-thread-3
2
线程:pool-1-thread-4
3
线程:pool-1-thread-5
4
线程:pool-1-thread-1
5
线程:pool-1-thread-2
6
线程:pool-1-thread-3
7
线程:pool-1-thread-4
8
线程:pool-1-thread-5
9

线程池的执行流程

1. 提交任务后,首先会判断核心线程数是否已满,若是在线程池中的线程数量没有达到核心线程数,这时会建立核心线程来执行任务;不然,进入下一步操做。

2. 接着判断线程池任务队列是否已满。若是没满,则将任务添加到任务队列中;不然,进入下一步操做。

3. 接着判断线程池中的线程数是否达到最大线程数(执行这步的前提是任务队列已满了)。若是未达到,则建立非核心线程执行任务;不然,就执行饱和策略,默认会抛出RejectedExecutionException异常。

线程类种类

java提供了常见四种线程池,分别能够经过Executors类获取:

FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor。

FixedThreadPool

经过Executors中的newFixedThreadPool方法来建立,该线程池是一种线程数量固定的线程池。

ExecutorService service = Executors.newFixedThreadPool(4);

 

在这个线程池中 所容纳最大的线程数就是咱们设置的核心线程数。 若是线程池的线程处于空闲状态的话,它们并不会被回收,除非是这个线程池被关闭。若是全部的线程都处于活动状态的话,新任务就会处于等待状态,直到有线程空闲出来。

因为newFixedThreadPool只有核心线程,而且这些线程都不会被回收,也就是 它可以更快速的响应外界请求 。从下面的newFixedThreadPool方法的实现能够看出,newFixedThreadPool只有核心线程,而且不存在超时机制,采用LinkedBlockingQueue,因此对于任务队列的大小也是没有限制的。

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

CachedThreadPool

经过Executors中的newCachedThreadPool方法来建立。

ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

经过newCachedThreadPool方法在这里咱们能够看出它的 核心线程数为0, 线程池的最大线程数Integer.MAX_VALUE。而Integer.MAX_VALUE是一个很大的数,也差很少能够说 这个线程池中的最大线程数能够任意大。当线程池中的线程都处于活动状态的时候,线程池就会建立一个新的线程来处理任务。该线程池中的线程超时时长为60秒,因此当线程处于闲置状态超过60秒的时候便会被回收。 这也就意味着如果整个线程池的线程都处于闲置状态超过60秒之后,在newCachedThreadPool线程池中是不存在任何线程的,因此这时候它几乎不占用任何的系统资源。

对于newCachedThreadPool他的任务队列采用的是SynchronousQueue,上面说到在SynchronousQueue内部没有任何容量的阻塞队列。SynchronousQueue内部至关于一个空集合,咱们没法将一个任务插入到SynchronousQueue中。因此说在线程池中若是现有线程没法接收任务,将会建立新的线程来执行任务。

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

 

ScheduledThreadPool

 

经过Executors中的newScheduledThreadPool方法来建立。

ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);

它的核心线程数是固定的,对于非核心线程几乎能够说是没有限制的,而且当非核心线程处于限制状态的时候就会当即被回收。

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

SingleThreadExecutor

经过Executors中的newSingleThreadExecutor方法来建立

ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();

在这个线程池中只有一个核心线程,对于任务队列没有大小限制,也就意味着这一个任务处于活动状态时,其余任务都会在任务队列中排队等候依次执行

newSingleThreadExecutor将全部的外界任务统一到一个线程中支持,因此在这个任务执行之间咱们不须要处理线程同步的问题。

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