加入收藏 | 设为首页 | 会员中心 | 我要投稿 辽源站长网 (https://www.0437zz.com/)- 云专线、云连接、智能数据、边缘计算、数据安全!
当前位置: 首页 > 服务器 > 搭建环境 > Unix > 正文

Java多线程优化都不会,怎么拿Offer?

发布时间:2020-01-07 21:28:51 所属栏目:Unix 来源:站长网
导读:副标题#e# 【51CTO.com原创稿件】随着业务量的增加,多线程处理成为家常便饭。于是,多线程优化成了摆在我们面前的问题。Java 作为当今主流的应用开发语言,也会有同样的问题。 图片来自 Pexels 今天,我们从 Java 内部锁优化,代码中的锁优化,以及线程池

Java多线程优化都不会,怎么拿Offer?

读写锁冲突示例图

上面基本把读写锁的基本原理说完了,接下来通过一些代码片段来看看它是如何实现的。

我们通过 Data 类 SharedResource,ReaderThread 和 WriterThread 来实现 Reader 和 Writer,ReadWriteLock 类来实现读写锁。

首先来看 ReaderThread 和 WriterThread,它们的实现相对简单。仅仅调用 Data 类中的 Read 和 Write 方法来实现读写操作。

Java多线程优化都不会,怎么拿Offer?

ReaderThread 对 Reader 的实现

Java多线程优化都不会,怎么拿Offer?

WriterThread 对 Writer 的实现

接下来就是 ReadWriteLock 类,它实现了读写锁的具体功能。其中的几个变量用来控制访问线程和写入优先级:

readingReaders:正在读取共享资源的线程个数,整型。

waitingWriters:正在等待写入共享资源的线程个数,整型。

writingWriters:正在写入共享资源的线程个数,整型。

preferWriter:写入优先级标示,布尔型,为 true 表示写入优先;为 false 表示读取优先。

里面包含了四个方法,分别是:

readLock

readUnlock

writeLock

writeUnlock

顾名思义,分别对应读锁定,读解锁,写锁定,写解锁的操作。两两组合以后一共四种方法。

Java多线程优化都不会,怎么拿Offer?

ReadWriteLock 示例图

在 ReadWriteLock 定义的四种方法中,各自完成不同的任务:

readLock,读锁。线程在读的时候,检查是否有写线程在执行,如果有就需要等待。同时还会观察,在写入优先的时候,是否有等待写入的线程。

如果存在也需要等待,等待写入操作的线程完成再执行。如果以上条件都没有满足,那么进行读操作,并将读取线程数 +1。

readUnlock,读解锁。线程在读操作完成以后,将读取线程数 -1。通知其他等待线程执行。

writeLock,写锁。先将写等待线程数 +1。如果发现有正在读的线程或者有正写的线程,那么进入等待。否则,进行写操作,并将正在写操作线程数 +1。

writeUnlock,写解锁。线程在写操作完成以后,将写线程数 -1。通知其他等待线程执行。

最后,来看共享资源的类:Data。它主要承载读写的方法。需要注意的是在做读/写的前后,需要加上对应的锁。

例如:在做读操作(doRead)之前需要加上 readLock(读锁),在完成读操作以后释放读锁(readUnlock)。

又例如:在做写操作(doWrite)之前需要加上 writeLock(写锁),在完成写操作以后释放写锁(writeUnlock)。

Java多线程优化都不会,怎么拿Offer?

共享资源类 Data 示例图

上面的几个类已经介绍完了,如果需要测试可以通过调用 ReaderThread 和 WriterThread 来完成调试。

Java多线程优化都不会,怎么拿Offer?

读写锁测试

线程池优化

前面两部分谈到多线程对内部锁的优化,以及代码中对锁的优化。是从减少竞态的角度来优化程序的。

如果从提高线程执行效率,来对多线程程序进行优化,自然让人联想到了线程池技术。

基本概念与原理

Java 线程池会生成一个队列,要执行的任务会被提交到这个队列中。有一定数量的线程会在队列中取任务,然后执行。

任务执行完毕以后,线程会返回任务队列,等待其他任务并执行。线程池中有一定数量的线程随时待命。

由于生成和维持这些线程是需要耗费资源了,维持太多或者太少的线程都会对系统运行效率造成影响,因此对线程池优化是有意义的。

Java多线程优化都不会,怎么拿Offer?

在做线程池调优之前,先介绍一下线程的几个基本参数,以及线程池运行的原理:

corePoolSize,线程池的基本大小,无论是否有任务需要执行,线程池中线程的个数。只有在工作队列占满的情况下,才会创建超出这个数量的线程。

maximumPoolSize,线程池中允许存在的最大线程数。

poolSize,线程池中线程的数量。

当提交任务需要流程池处理时,会经过以下判断:

线程池中的线程数还没有达到基本大小,也就是 poolSize

线程池中的线程数大于或等于基本大小,也就是 poolSize>=corePoolSize,并且任务队列未满时,将任务提交到阻塞队列排队等候处理。

如果当前线程池的线程数大于或等于基本大小,也就是 poolSize>=corePoolSize 且任务队列占满时,需要分两种情况考虑。

①当 poolSize<maximumPoolSize,新增线程来处理任务;②当 poolSize=maximumPoolSize,线程池的处理能力达到极限,因此拒绝新增加的任务。

线程池容量配置

从上面线程池原理可以看出,corePoolSize 设置是整个线程池中最关键的参数。

如果设置太小会导致线程池的吞吐量不足,因为新提交的任务需要排队或者被拒绝处理;设置太大可能会耗尽计算机的 CPU 和内存资源。

那么如何配置合理的线程池大小呢?如果将被处理的任务分为,CPU 密集型任务和 IO 密集型任务。前者需要更多 CPU 的运算操作,后者需要更多的 IO 操作。

CPU 密集型任务应配置尽可能小的线程,如配置 CPU 个数 +1 的线程数,IO 密集型任务应配置尽可能多的线程,因为 IO 操作不占用 CPU,不要让 CPU 闲下来,应加大线程数量,如配置两倍 CPU 个数 +1。

CPU 的数字是一个假设,实际环境中需要进行测试,这里给大家一个思路。

若任务对其他系统资源有依赖,如任务依赖数据库返回的结果(IO 操作)。其等待时间越长,CPU 空闲时间就越长,那么线程数量应该越大,才能更好的利用 CPU。

因此在 IO 优化中发现一个估算公式:

最佳线程数目=((线程等待时间+线程 CPU 时间)/线程 CPU 时间 )* CPU 数目。

将公式进一步化简,得到:

最佳线程数目= (线程等待时间与线程 CPU 时间之比+1)* CPU 数目。

因此得到结论:线程等待时间所占比例越高,需要越多线程。线程 CPU 时间所占比例越高,需要越少线程。

从另外一个角度验证上面对 IO 密集型(线程等待时间占比高)和 CPU 密集型(CPU 时间占比高)设置线程池大小的想法。

总结

Java 多线程开发优化有两个思路:

针对锁的优化

线程池优化

我们从内部锁优化原理入手,分别介绍了锁消除,锁粗化,偏向锁,适应锁,都是以 Java 系统本身来做优化的,作为程序员需要了解其实现原理。

针对 Java 代码中锁的优化,我们又提出了,减少临界区范围,减小锁的颗粒度,读写锁(设计模式)等方法。

其中,读写锁只是多线程设计模式中的一种,如果有兴趣可以扩展阅读其他的设计模式,协助进行多线程开发。最后针对线程池实现原理,提出了设置线程池大小的思路。 作者:崔皓

简介:十六年开发和架构经验,曾担任过惠普武汉交付中心技术专家,需求分析师,项目经理,后在创业公司担任技术/产品经理。善于学习,乐于分享。目前专注于技术架构与研发管理。

(编辑:辽源站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

推荐文章
    热点阅读