专栏首页技术从心常见的并发场景

常见的并发场景

线程池

并发最常见用于线程池,显然使用线程池可以有效的提高吞吐量。

最常见、比较复杂一个场景是Web容器的线程池。Web容器使用线程池同步或者异步处理HTTP请求,同时这也可以有效的复用HTTP连接,降低资源申请的开销。通常我们认为HTTP请求时非常昂贵的,并且也是比较耗费资源和性能的,所以线程池在这里就扮演了非常重要的角色。

在线程池的章节中非常详细的讨论了线程池的原理和使用,同时也提到了,线程池的配置和参数对性能的影响是巨大的。不尽如此,受限于资源(机器的性能、网络的带宽等等)、依赖的服务,客户端的响应速度等,线程池的威力也不会一直增长。达到了线程池的瓶颈后,性能和吞吐量都会大幅度降低。

一直增加机器的性能或者增大线程的个数,并不一定能有效的提高吞吐量。高并发的情况下,机器的负载会大幅提升,这时候机器的稳定性、服务的可靠性都会下降。

尽管如此,线程池依然是提高吞吐量的一个有效措施,配合合适的参数能够有效的充分利用资源,提高资源的利用率。

任务队列

除了线程池是比较发杂的并发场景外,任务队列也是一个不错的并发工具。JDK内部有大量的队列(Queue),这些工具不仅能够方便使用,提高生产力,也能够进行组合适应于不同的场景。即使线程池内部,也是用了任务队列来处理任务的积压,平衡资源的消耗。

安全的任务队列能够有效的平衡机器的复杂,抵消由于峰值和波动带来的不稳定,有效提高服务的可靠性。同时任务队列的处理也有助于统计和分析服务的状况。

任务队列也可以在多个线程之间传递数据,有助于并行处理任务。例如经典的“生产者-消费者”模型就可以有效的提高多个线程的并行处理能力。在IO延时比较大的服务中尤其有效。我最喜欢的一个案例是导数据是,一个线程负责往固定大小的任务队列中压入大量的数据,队列满了以后就暂停,另外几个线程负责从任务队列中获取数据并消费。这将串行的“生产-消费”,变成了并行的“生产-消费”。实践证明极大的节省任务处理时间。

异步处理

线程池也是异步处理的一种表现形式,除此之外,使用异步处理的目的也是为了提高服务的处理速度。例如AOP的一个例子就是使用切面来记录日志,如果说我们要远程收集日志,显然不希望由于收集日志而影响服务本身。这时候就将日志收集的过程进行异步处理。

如今大量的开源组件都喜欢使用异步处理来提高IO的效率,某些不需要同步返回的操作使用异步处理后能够有效的提高吞吐量。

当然,异步也不总是令人满意的,也会有相应的问题。例如引入异步设计后的复杂性,线程中断后的处理机制,失败后的处理策略,产生的消息比消费的还快时怎么办,关闭程序时如何关闭异步处理逻辑等等。这都会增加系统的复杂性。

尽管大量的服务、业务使用异步来处理,但是很显然需要有保障机制能够保证异步处理的逻辑正确性。如果认为异步处理的任务不是特别重要,或者说主业务不能因为附属业务的逻辑出错而崩溃,那么使用异步处理是正确的选择。

同步操作

并发操作的同时还需要维护数据的一致性,或多或少的会涉及到同步操作。正确的使用原子操作,合理的使用独占锁和读写锁也是一个很大的挑战。

线程间的协调与通信,尤其是状态的同步都是比较困难的。我们看到线程池ThreadPoolExecutor的实现为了解决各个线程的执行状态,引入的很多的同步操作。线程越来越多的情况下,同步的成本会越来越高,同时也有可能引入死锁的情况。

尽管如此,单个JVM内部的多线程同步还是比较容易控制的。JDK内部也提供了大量的工具来方便完成数据的同步。例如Lock/Condition/CountDownLatch/CyclicBarrier/Semaphore/Exchanger等等。

分布式锁

分布式的并发问题更难以处理,根据CAP的原理,基本上没有一个至善至美的方案。分布式资源协调使用分布式锁是一个不错的选择。Google的分布式锁(建立在BigTable之上),Zookeeper的分布式锁,甚至简单的利用memcache的add操作或者redis的setnx操作建立伪分布式锁也可以解决类似的问题。

本文分享自微信公众号 - 技术从心(gh_d845efe513db),作者:LYJ

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 线程池的初步了解

    运行一下,我这里第一段代码使用了线程池的时间是194ms,第二段代码不使用线程池的时间是2043ms。这里默认的线程池中的线程数是100,如果把这个数量减小,虽...

    技术从心
  • Java线程池了解一下?

    其实常用Java线程池本质上都是由ThreadPoolExecutor或者ForkJoinPool生成的,只是其根据构造函数传入不同的实参来实例化相应线程池而已...

    技术从心
  • 40个Java多线程问题总结

    一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓"知其然知其所以然","会用"只是"知其然","为什么用...

    技术从心
  • 【Java 并发】 之 AQS 详解 & volatile关键字CPU内存架构volatile关键字的作用

    谈到并发,不得不谈ReentrantLock;而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)!

    一个会写诗的程序员
  • java多线程关键字volatile、lock、synchronized

    总结:volatile关键字的作用是:使变量在多个线程间可见(具有可见性),但是仅靠volatile是不能保证线程的安全性,volatile关键字不具备sync...

    刘文正
  • [051]Binder线程优先级继承

    Binder通信需要两个线程,这两个线程的优先级是不同,也就意味着,他们能获取到的cpu的优先级不同。

    王小二
  • Java线程池监控小结

    最近我们组杨青同学遇到一个使用线程池不当的问题:异步处理的线程池线程将主线程hang住了,分析代码发现是线程池的拒绝策略设置得不合理,设置为CallerRuns...

    阿杜
  • Java中高级面试题部分答案解析(4)

    这里选了几道高频面试题以及一些解答。不一定全部正确,有一些是没有固定答案的,如果发现有错误的欢迎纠正,如果有更好的回答,热烈欢迎留言探讨。

    java思维导图
  • 100道Java并发和多线程基础面试题大集合(含解答),这波面试稳了~

    这些多线程的问题来源于各大网站,可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都看过,但是本文写作的重心就是所有的问题都会按照自己的理解回...

    程序员白楠楠
  • 线程的创建

    1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。

    黑洞代码

扫码关注云+社区

领取腾讯云代金券