Java面向对象系列[v1.0.0][线程池]

系统启动一个新的线程需要与操作系统进行交互,往往消耗的成本比较高,在某些情况下,使用线程池可以很好的提升性能,尤其当成中需要创建大量生存期很短的的线程时,更应该考虑使用线程池

线程池在系统启动时就创建大量的空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个空闲的线程来执行他们的run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法
此外,线程池还可以有效的控制并发线程的数量,它的最大线程数参数可以控制系统中并发线程数不超过此数

Java5之前必须手动实现自己的线程池,从Java5开始Java内建支持线程池,Java5新增了一个Executors工厂类来产生线程池,该工厂类包含如下几个静态工厂方法来创建线程池

  • newCachedThreadPool(): 创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中,该方法返回一个ExecutorService对象,该对象代表一个线程池,他可以执行Runnable对象或Callable对象所代表的线程
  • newFixedThreadPool(int nThreads): 创建一个可重用的、具有固定线程数的线程池,该方法返回一个ExecutorService对象,该对象代表一个线程池,他可以执行Runnable对象或Callable对象所代表的线程
  • newSingleThreadExecutor(): 创建一个只有单线程的线程池,它相当于调用newFixedThreadPool()方法时传入参数为1,该方法返回一个ExecutorService对象,该对象代表一个线程池,他可以执行Runnable对象或Callable对象所代表的线程
  • newScheduledThreadPool(int corePoolSize): 创建只有指定线程数的线程池,它可以在指定延迟后执行线程任务,corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内,该方法返回一个ScheduledExecutorService线程池,它是ExecutorService的子类,它可以在指定延迟后执行线程任务
  • newSingleThreadScheduledExecutor(): 创建只有一个线程的线程池,它可以在指定延迟后执行线程任务,该方法返回一个ScheduledExecutorService线程池,它是ExecutorService的子类,它可以在指定延迟后执行线程任务
  • ExecutorService newWorkStealingPool(int parallelism): 创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来竞争,Java8新增的该方法可充分利用多CPU并行的能力,它生成的work stealing池相当于后台线程池,如果所有的前台线程都死亡了,work stealing池中的线程会自动死亡
  • ExecutorService newWorkStealingPool(): 该方法是前一个方法的简化版,如果当前机器有4个CPU,则目标并行级别被设置为4,也就是相当于为前一个方法传入4作为参数,Java8新增的该方法可充分利用多CPU并行的能力,它生成的work stealing池相当于后台线程池,如果所有的前台线程都死亡了,work stealing池中的线程会自动死亡

ExecutorService代表尽快执行线程的线程池,意思就是只要线程池中有空闲线程,就立即执行线程任务,程序只要将一个Runnable对象或Callable对象(线程任务)提交给该线程池,该线程池就会尽快执行改任务,ExecutorService提供了3个方法:

  • Future<?> submit(Runnable task): 将一个Runnable对象提交给指定的线程池,线程池将在有空闲线程时执行Runnable对象代表的任务,其中Future对象代表Runnable任务的返回值,但是run()方法没有返回值,所以Future对象将在run()方法执行后,返回null,但是可以调用Future的isDone()、isCancelled()方法来获得Runnable对象的执行状态
  • Futuresubmit(Runnable task, T result): 将一个Runnable对象提交给指定的线程池,线程池将在有空闲线程时执行Runnable对象代表的任务,其中Result显示的指定线程执行结束后的返回值,所以Future对象将在run()方法后返回result
  • Futuresubmit(Callabletask):将一个Callable对象提交给指定的线程池,线程池将在有空闲线程时执行Callable对象代表的任务,其中Future代表Callable对象里call()方法的返回值

ScheduledExecutorService代表可在指定延迟后或周期性地执行线程任务的线程池,它提供了4个方法:

  • ScheduledFutureschedule(Callablecallable, long delay, TimeUnit unit):指定callable任务将在delay延迟后执行
  • ScheduledFuture<?>schedule(Runnable command, long delay, TimeUnit unit):指定command任务将在delay延迟后执行
  • ScheduledFuture<?>scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):指定command任务将在delay延迟后执行,在initialDelay后开始执行,一次在initialDelay+period、initialDelay+2*period…后重复执行,依次类推
  • ScheduledFuture<?> scheduleWithFixedDealy(Runnable command, long initialDealy, long delay, TimeUnit unit):创建并执行一个在给定初始延迟后启用的定期操作,随后在每一次执行终止和下一次执行开始之间都存在给定的延迟,如果任务在任意一次执行时遇到异常,就会取消后续执行,否则只能通过程序来显示取消或者终止该任务

关闭线程池

用完一个线程池后,应该调用该线程池shutdown()方法,该方法将启动线程池的关闭序列,调用该方法后线程池不再接受新任务,但会将以前所有已提交的任务执行完成,当线程池中的所有任务都执行完成后,池中的所有线程都会死亡
也可以调用线程池的shutdownNow()方法来关闭线程池,该方法会试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表

使用线程池步骤

  • 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池
  • 创建Runnable实现类或Callable实现类的实例,作为线程执行任务
  • 调用ExecutorService对象的submit()方法来提交Runnable实例或者Callable实例
  • 当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池
import java.util.concurrent.*;

public class ThreadPoolTest
{
	public static void main(String[] args)
		throws Exception
	{
		// 创建足够的线程来支持4个CPU并行的线程池
		// 创建一个具有固定线程数(6)的线程池
		ExecutorService pool = Executors.newFixedThreadPool(6);
		// 使用Lambda表达式创建Runnable对象
		Runnable target = () -> {
			for (var i = 0; i < 100; i++)
			{
				System.out.println(Thread.currentThread().getName() + "的i值为:" + i);
			}
		};
		// 向线程池中提交两个线程
		pool.submit(target);
		pool.submit(target);
		// 关闭线程池
		pool.shutdown();
	}
}

ForkJoinPool

为了充分利用多CPU和多核CPU的优势,可以把一个任务拆分成多个“小任务”,把多个小任务放到多个处理器核心上并行执行,多个小任务执行完成后,再将这些执行结果合并起来即可,Java7提供了ForkJoinPool来支持这个操作
ForkJoinPool是ExecutorService的实现类,因此它是一个特殊的线程池,其常用构造器如下:

  • ForkJoinPool(int parallelism):创建包含parallelism个并行线程的ForkJoinPool
  • ForkJoinPool(): 以Runtime.availableProcessors()方法的返回值作为parallelism参数来创建ForkJoinPool

Java8扩展了ForkJoinPool,增加了两个静态方法提供通用池功能

  • ForkJoinPool commonPool():该方法返回一个通用池,通用池的运行状态不会受shutdown()或shutdownNow()方法的影响,当然如果程序直接执行System.exit(0);来终止虚拟机,通用池以及通用池中正在执行的任务都会被终止
  • int getCommonPoolParallelism():该方法返回通用池的并行级别

使用ForkJoinPool

创建了ForkJoinPool实例后,就可以调用ForkJoinPool的submit(ForkJoinTask task)或者invoke(ForkJoinTask task)方法来执行指定任务。
ForkJoinTask代表一个可以并行、合并的任务,ForkJoinTask是一个抽象类它还有两个抽象子类RecursiveAction和RecursiveTask,其中RecursiveAction代表没有返回值的任务,RecursiveTask代表有返回值的任务
在这里插入图片描述

import java.util.concurrent.*;


// 继承RecursiveAction来实现"可分解"的任务
class PrintTask extends RecursiveAction
{
	// 每个“小任务”只最多只打印50个数
	private static final int THRESHOLD = 50;
	private int start;
	private int end;
	// 打印从start到end的任务
	public PrintTask(int start, int end)
	{
		this.start = start;
		this.end = end;
	}
	@Override
	protected void compute()
	{
		// 当end与start之间的差小于THRESHOLD时,开始打印
		if (end - start < THRESHOLD)
		{
			for (var i = start; i < end; i++)
			{
				System.out.println(Thread.currentThread().getName() + "的i值:" + i);
			}
		}
		else
		{
			// 如果当end与start之间的差大于THRESHOLD时,即要打印的数超过50个
			// 将大任务分解成两个小任务。
			int middle = (start + end) / 2;
			var left = new PrintTask(start, middle);
			var right = new PrintTask(middle, end);
			// 并行执行两个“小任务”
			left.fork();
			right.fork();
		}
	}
}
public class ForkJoinPoolTest
{
	public static void main(String[] args)
		throws Exception
	{
		var pool = new ForkJoinPool();
		// 提交可分解的PrintTask任务
		pool.submit(new PrintTask(0, 300));
		pool.awaitTermination(2, TimeUnit.SECONDS);
		// 关闭线程池
		pool.shutdown();
	}
}
import java.util.concurrent.*;
import java.util.*;


// 继承RecursiveTask来实现"可分解"的任务
class CalTask extends RecursiveTask<Integer>
{
	// 每个“小任务”只最多只累加20个数
	private static final int THRESHOLD = 20;
	private int arr[];
	private int start;
	private int end;
	// 累加从start到end的数组元素
	public CalTask(int[] arr, int start, int end)
	{
		this.arr = arr;
		this.start = start;
		this.end = end;
	}
	@Override
	protected Integer compute()
	{
		int sum = 0;
		// 当end与start之间的差小于THRESHOLD时,开始进行实际累加
		if (end - start < THRESHOLD)
		{
			for (var i = start; i < end; i++)
			{
				sum += arr[i];
			}
			return sum;
		}
		else
		{
			// 如果当end与start之间的差大于THRESHOLD时,即要累加的数超过20个时
			// 将大任务分解成两个小任务。
			int middle = (start + end) / 2;
			var left = new CalTask(arr, start, middle);
			var right = new CalTask(arr, middle, end);
			// 并行执行两个“小任务”
			left.fork();
			right.fork();
			// 把两个“小任务”累加的结果合并起来
			return left.join() + right.join();    // ①
		}
	}
}
public class Sum
{
	public static void main(String[] args)
		throws Exception
	{
		var arr = new int[100];
		var rand = new Random();
		var total = 0;
		// 初始化100个数字元素
		for (int i = 0, len = arr.length; i < len; i++)
		{
			int tmp = rand.nextInt(20);
			// 对数组元素赋值,并将数组元素的值添加到sum总和中。
			total += (arr[i] = tmp);
		}
		System.out.println(total);
		// 创建一个通用池
		ForkJoinPool pool = ForkJoinPool.commonPool();
		// 提交可分解的CalTask任务
		Future<Integer> future = pool.submit(new CalTask(arr, 0, arr.length));
		System.out.println(future.get());
		// 关闭线程池
		pool.shutdown();
	}
}


发布了214 篇原创文章 · 获赞 153 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/dawei_yang000000/article/details/105563504