前言
多线程相关的根底,相信咱们都知道,可是在实在的开发中运用到多线程,的确少之又少。但其实多线程开发大有用途,仅仅许多时候咱们都是不得已才去运用,而不是会主动考虑某个功用是否要运用多线程去实现。在线开发也稍微有点年限,这儿能够浅谈一下我对实战中运用多线程的了解。
1. 运用危险
讲运用之前,仍是有必要要着重一下危险,这也是咱们学这块根底知识的时候或许说一些面试,都会要着重的。由于多线程的环境中会存在一些危险,要在实战中更好的运用多线程,那就有必要了解他可能会导致的问题。
举个比方,有时候运用中会呈现一些古怪的现象,比方我分明改了这个目标,可是却没改成功,我分明做了更新,但终究的体现仍是更新前的现象等等。多线程的问题会导致一些古怪的现象产生,而这些现象根本的共同点就对错必现、难排查。有经验的朋友应该都知道,这类现场相关的问题是真的很难排查,有必要要花费一些精力才干处理。通俗来讲便是它不难,可是它能让你掉一层皮。
所以多线程相关的根底知识很重要,原子性等3个特性、加锁、各种锁、休眠等根底的知识点一定要都了解了再来运用,这篇文章我就不总结这些根底知识了。
总结来说便是,在运用的时候,一定要认真考虑此处运用多线程会不会在某种状况下导致某些异常现象,这是其一,其二便是更深层次的考虑,以后的有人来修正、扩展这块功用,这儿的多线程是否会对其造成某些影响。
2. 和流程无相关的过程能够运用多线程
这是一个比较万金油的场景,假设我把当时的页面的事务逻辑细分为许多个使命,task1 … taskn,我的task2要拿task1的成果才干进行,可是task3不需求任何前置的操作,并且当时运用的一切使命,不需求拿task3的成果,那这个task3便是一个和流程无关的操作,假如它是一个比较耗时的操作,那就能够用多线程去实现。
举个比方,我曾经有碰到过这样的需求,运用进入主页后,要上传日志,而这个日志又要做一些截取关键字相关内容,不上报悉数。咱们都知道,android的日志会有许多内容,所以遍历它会是一个耗时的操作。可是这儿的使命仅仅遍历截取上报日志文件,和其它的使命、过程没有任何相关。日志能直接从设备文中拿,终究上传后就完毕了。上传的流程必定是异步在其他线程进行,可是默许的状况下,遍历和截取内容的操作是在主线程进行的,而这又是一个耗时操作,所以能够开一条线程去做。
ok,然后这事还没完,咱们还得考虑危险是不是,那上面那个比方的危险是什么,假如上传过程中,我退出页面再重进,又传一次,这必定不可,我必定只需求一份日志就行,重复传多次对我来说仅仅浪费资源,所以我要用一个状况来表明整个流程,假如正在上传就不重复传,那这个状况就会触及到多线程的问题,你就得想办法保证这个状况的可见性。
3. 杂乱的核算能够考虑放在子线程中进行
这种状况一般操作都是开一条线程做耗时的核算操作,然后再把核算的成果给到主线程,怎样给?经过handler啊,否则面试官老考你handler干嘛。
这个杂乱的核算一般场景都不会碰到,假如你的数据源有很大的数据,然后你的成果是依据这个庞大的数据源去核算出来的,比较经典的便是O(n^m)这种状况,核算这个数据套了许多层for
或许比较经典的便是绘制杂乱的矢量图,由于你要依据数据去核算出各个点位,而杂乱的矢量图往往需求比较多的点位,比方说你做股票软件的,你的折线图便是要有许多点位,这些点位是依据后台返回的数据去算出来的,算他们的xy坐标 (当然假如你的股票每天都跌停那就不必算了【狗头】)
假设你的核算悉数放在主线程做,假如在onDraw方法中做,咱们能够简略看看onDraw的线程
能够看到onDraw里边的操作是在主线程中的,你在主线程做耗时核算,你不卡谁卡。那要怎样改,大致能够这样(这儿仅仅写个思路)
那这个状况下有什么危险,首先这个状况,假如你在子线程去改的话,就会有多线程问题,假如用handler发消息给主线程去改的话就不会有问题。其次这个依据源数据去核算的点位数组,假如在子线程核算中,主线程又setData设置了新的源数据,能够让线程中止,重新核算。
4. 拿多个无关使命的成果
举个比方,我有3个恳求,这3个恳求毫无相关,可是我终究要拿着3个恳求的成果一起做操作。
这种状况下就应该开线程去做,我敢说大部分人都是做成线性的,而不是并行的。
比方3个毫无相关的使命,咱们仅仅终究要兼并他们的成果,那分明能够这样做
可是有许多人都是在实战中这样做
无非便是觉得线性的操作便利,并行的操作费事并且有危险。可是假如这3个task是网络恳求,你要知道网络恳求可是耗时操作哦,你可能觉得可是你测试的时候线性操作也很快,可是你是不是要考虑网络慢的状况。
像这种状况你运用countdownlatch、handler等方法都是能做到并行的作用。
至于危险,这个状况下运用多线程当然是有危险,但我以为这也是一个比较经典的状况,这种状况,你不能由于觉得有危险,就完全放弃运用多线程,而是要用其他办法去避免危险的发生。
5. 初始化、预加载
一个比较经典的状况,你进主页卡顿,进运用主页慢,其中一个原因便是你在主页的onCreate方法中做了大量的初始化操作。
那么你是否有考虑过把初始化的操作,放到子线程进行。你也许考虑过,可是你忧虑,你忧虑运用的时候还没初始化完结。
其实这种状况下也有许多种做法,首先你要处理进主页慢的问题,那自然是得开线程去做了,所以咱们要处理的问题是在开线程去做初始化的根底上怎样去避免说我要用到这个功用可是还没初始化的状况。
这个初始化,也是能分不同的类型的,比方我对设备信息初始化,进主页后我要获取设备的信息,并且这些设备的信息是许多当地都要用到的,比方一切的恳求都要传设备ID等,那这个设备信息的初始化就很重要,我就能够把它放在主线程中进行,不要开子线程去做,否则用到的当地太多了,都去适配很费事。
可是有些初始化操作,比方一些第三方SDK,比方你接了付出的SDK,微信付出宝,你只有在翻开付出页面的时候才会用到,那这个当地的初始化完全能够放到子线程去做,至于你忧虑的运用到的时候还没初始化完结。比方你忧虑都翻开付出页面了,付出SDK的初始化还没完结(我算你手快的状况)。那也完全能够处理,你能够用一个状况表明当时是否现已初始化了,然后对行为做lazy,假如现已初始化了直接进行,假如还没初始化,则等初始化之后再做lazy操作。
我这儿能够写一段代码,大约便是这个意思,主要是一个思路
预加载也同理,假如一些大资源加载慢,也能够经过开条线程先去做预加载。
6. 总结
这儿列举了一些比较常见的在开发中能够运用多线程进行开发的场景。
其实许多当地用多线程进行处理的话,更为合理,仅仅许多时候有些人会碍于多线程的危险或许代码的杂乱度,所以仍是选择了悉数放在主线程去做。
对于代码的杂乱度,我只能说写多了就习惯了,并且你得去写这些东西才干有进步,你想对这块范畴有更深入的了解,就要去写。至于危险,我以为并不是就一味的觉得有危险就不去做,比方危险太大,太杂乱,ok,我能够先不做,可是危险没这么大,思路又比较清晰的状况,咱们不是不去做,而是考虑怎样去处理这些危险。
所以面试有时候会问,你开发有没有触及到多线程的状况,你看我上面举的比方,我感觉根本每个项目都会触及到,仅仅你不敢去朝这个方向去写。假如你答复没触及过,那我要么就觉得你不太行,要么就觉得你的项目太小,太简略,无论是哪种状况,面试官对你必定要减分。
整篇文章说的“开一条线程”,指的是用多线程去做这件事,而不是指直接new Thread,由于开线程的方法许多,java为什么直接供给给你HandlerThread,为什么给你供给CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadPool 四种线程池,都是有原因的。