线程池
并发最常用于线程池,显然使用线程池可以有效地提高吞吐量。
最常见、最复杂的场景是Web容器的线程池。Web容器使用线程池同步或异步处理HTTP请求,也可以有效地重复使用HTTP连接,降低资源应用的成本。通常我们认为HTTP请求非常昂贵,也消耗资源和性能,所以线程池在这里起着非常重要的作用。
在线程池的章节中,详细讨论了线程池的原理和使用,并提到线程池的配置和参数对性能有很大的影响。此外,它仅限于资源(机器性能、网络带宽等)、依靠服务、客户端响应速度等,线程池的力量不会一直增加。在线程池达到瓶颈后,性能和吞吐量将大大降低。
不断提高机器性能或线程数量并不一定能有效提高吞吐量。当机器的稳定性和服务的可靠性降低时,机器的负载会大大提高。
尽管如此,线程池仍然是提高吞吐量的有效措施,配合适当的参数可以有效地充分利用资源,提高资源利用率。 任务队列
任务队列除了线程池是复杂的并发场景外,也是一个很好的并发工具。JDK内部有大量的队列(Queue),这些工具不仅可以方便地使用,提高生产力,还可以适应不同的场景。即使在线程池内,任务队列也用于处理任务积压,平衡资源消耗。
安全的任务队列可以有效地平衡机器的复杂性,抵消峰值和波动带来的不稳定性,有效地提高服务的可靠性。同时,任务队列的处理也有助于统计和分析服务状况。
任务队列还可以在多个线程之间传输数据,有助于并行处理任务。例如,经典的“生产者-消费者”模型可以有效地提高多线程的并行处理能力。在IO延迟较大的服务中尤为有效。 我最喜欢的案例之一是导数据是,一个线程负责将大量的数据压入固定大小的任务队列,在队列满后暂停,其他几个线程负责从任务队列中获取和消费数据。这将串行的“生产-消费”变成了并行的“生产-消费”。实践证明,任务处理时间大大节省。 异步处理
线程池也是异步处理的一种表现形式。此外,异步处理的目的是提高服务的处理速度。 例如,AOP的一个例子是用截面记录日志。如果我们想远程收集日志,我们显然不想因为收集日志而影响服务本身。此时,日志收集过程将进行异步处理。
如今,大量的开源组件喜欢使用异步处理来提高IO的效率,一些不需要同步返回的操作可以有效地提高吞吐量。
当然,异步并不总是令人满意的,也会有相应的问题。例如,引入异步设计后的复杂性、线程中断后的处理机制、失败后的处理策略、产生的消息比消费更快、关闭程序时如何关闭异步处理逻辑等。这将增加系统的复杂性。
虽然大量的服务和业务使用异步处理,但显然需要一个保证机制来确保异步处理的逻辑正确性。如果认为异步处理的任务不是特别重要,或者主要业务不能因为附属业务的逻辑错误而崩溃,那么使用异步处理是正确的选择。 同步操作
并发操作还需要维护数据的一致性,或多或少涉及同步操作。正确使用原子操作,合理使用独家锁和读写锁也是一个巨大的挑战。
线程之间的协调和通信,特别是状态的同步相对困难。我们可以看到,为了解决每个线程的执行状态,引入了大量的同步操作。随着线程越来越多,同步成本将越来越高,也可能引入死锁。
尽管如此,单个JVM内部的多线程同步相对容易控制。JDK还提供了大量工具来完成数据同步。例如,Lock/Condition/CountDownLatch/CyclicBarrier/Semaphore/Exchanger等。 分布式锁
根据CAP的原理,分布式并发问题更难处理,基本上没有完美的解决方案。 分布式锁是分布式资源协调使用的好选择。谷歌的分布式锁(基于bigtable)、Zookeper的分布式锁,甚至简单地使用memcache的addd操作或redisetnx操作来建立伪分布式锁也可以解决类似的问题。