java并发编程(六)任务执行

大多数并发应用都是围绕任务执行来构造的,任务通常是一些抽象且离散的工作单元

在线程中执行任务

在线程中执行任务大致分为两种情况。一种是单线程串行化执行任务,另一种是为每一个任务创建线程来执行。

串行化执行任务
在单线程中串行的执行各项任务是执行任务最简单的策略。
串行化执行任务的缺点是无法提供高吞吐率以及快速响应。
但是串行化执行任务的优点是更加的简单和安全。
所以当任务比较少且执行时间很长,简单说就是不需要高吞吐率和快速相应时使用串行处理机制是一种很好的选择。

显示的为任务创建线程

通过为每一个任务创建线程的方式执行任务,可以提供更高的响应性和吞吐率。
但是这种方式的缺点也很明显。

  1. 线程生命周期的开销非常大,线程的创建和销毁都是有不小的开销的。如果大量的轻量级请求使用这种方式,这些额外开销是很大的,典型的像聊天软件如果使用这种方式,额外开销是很吓人的。
  2. 资源消耗活跃的线程会消耗资源,尤其是内存。但是闲置的线程也会占用很多内存,给垃圾回收器带来压力。而且大量线程竞争cpu资源也会消耗资源。如果项目中已经有足够多的线程使cpu保持忙碌,那么创建更多的线程反而会降低性能。
  3. 稳定性 jvm的参数,线程请求栈的大小等等因素都会限制可创建线程的数量,当这个数量超出后可能会抛出OutOfMemoryException异常。

综合来说这种方式只能在一定范围内提高吞吐率和响应性,超出这个范围只会降低系统性能。

线程池执行任务
两种方式都存在这一些严格的限制,所以日常开发中我们更多的会使用线程池来执行任务。

线程池中存在阻塞队列也叫工作队列,在队列中保存所有待执行的任务。由线程池内创建的线程来执行这些任务。

跟前两种方式相比,线程池通过会重用内部创建的线程,当线程执行完一个任务后会执行下一个任务,这种方式避免了频繁创建和销毁线程带来的开销,同时我们可以通过限制线程池的大小来保持处理器的忙碌状态,防止大量线程竞争处理器资源。
ExecutorService和Executor的区别

  • 1.ExecutorService 接口继承了Executor 接口,是Executor 的子接口。

  • 2.Executor接口中定义了execute()方法,用来接收一个Runnable接口的对象,而ExecutorService接口中定义的submit()方法可以接收Runnable和Callable接口对象。

  • 3.Executor接口中execute()方法不返回任何结果,而ExecutorService接口中submit()方法可以通过一个 Future 对象返回运算结果。

  • 4.Executor和ExecutorService除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。

关于线程池会在后续的博客中做更详细的讲解。

callable和future

Callable是一种更好的执行任务的方式,它可以有返回值而且可以抛出异常。运行Callable任务可以拿到一个Future对象。

Future可以表示一个任务的生命周期,包括等待,执行,完成。
future.get方法的结果取决于future的状态,完成之前是阻塞的,完成后会得到返回值或者抛出异常。
future.get还可以传入时间参数,在该时间内如果任务没有执行完成则会抛出TimeOutException异常,通过捕捉这个异常我们可以在任务执行时间过长时去处理这个任务。

注意

当两个任务互相依赖时,如果一个任务执行很慢会拖累另一个任务的执行时间。最终导致性能没有提高很多,代码却变得更复杂了。
只有当对大量相互独立的任务进行并发处理时才能体现出并发处理任务带来的真正的性能提升。
如果迫不得已要对一个执行时间很长的任务进行拆分的话一定要注意尽量保证各子任务之间的相互独立。

发布了56 篇原创文章 · 获赞 4 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/xs925048899/article/details/104654247