多线程的引入是为了提高cpu的利用率的,无论是单核时代,对于任何阻塞(大多数是io阻塞)期间cpu处于空闲的不忍直视,还是多核时代,对于闲置核心的置之不理,多线程必须站出来。
有人说,并发问题,是多线程的出现而产生的问题。其实,还真是多线程为并发问题背的锅。
早在多道程序时代,操作系统刚刚雏形的阶段,就已经有了多个程序间对于内存的并发问题,只不过内存管理提前做好了内存分配的一刀切,直接让进程分家了,总不能还把别人家的孩子当自家孩子拎起来揍上一通。包括现在的分布式系统,多个应用节点间对于共享资源的并发控制,都在说明并发问题发生在任何多个活动载体(最小是线程)对于同一资源进行操作的场景里。
一般多线程的并发问题,也成为线程安全问题。
所以,很多同步工具,同步关键字,锁,cas机制(乐观锁)都是对并发的控制。只不过,同步关键字,是对对象资源做访问屏蔽,锁,是对线程做访问屏蔽。
原生同步关键字,适合对单资源进行锁定,无锁,就是逻辑上无线程不安全的问题,偏向锁,在持续一段时间内,认为这段数据只有自己操作的可能性最大,所以就不释放了,下次来还是自己的,潇洒的一匹。当然,线程执行结束后,别人来获取这把锁,他当然可以获取,反正原主人都死去了。如果主人还活着,那么不好意思,原主人发现自己判断失败,还是把锁升级成轻量锁吧。
其实,直接由偏向锁升级而来的轻量锁,未必很合适。轻量锁,面对的问题是,我可能占用锁的时间极短,如果等待的人都直接阻塞掉,重新解除阻塞代价很大的,还不如稍等一会儿,你直接拿来用多方便。当然,一旦等的时间太久,估计就得骂娘了(其实,主要是对cpu浪费很大,就是占用着空转了),那么现主人又悟了,原来对资源的操作是比较久的撒,所以直接把锁升级成重量锁,大家也别搁这里等了,天这么热也晒得慌,都各回各家睡觉去吧(线程进入阻塞状态,然后解放了cpu ,它开心的又可以撒欢了),等我完事了再通知你们。
所以,同步关键字适合的是,极低的并发几率,以及锁住的资源,操作很快就可以释放掉。(本段文字中的主人都是对象,对象来负责屏蔽其他线程的访问)
那么,锁呢,就相当于门卫了,他决定了一次可以进一个还是多个(独占锁与共享锁,读写锁),还是排队进入或者小伙子眉清目秀你先来(公平锁与非公平锁),又或是你小子站住刚见你进去过咋又来一次都没见你出来(可重入),所以锁管理的是线程,当然锁的核心是队列同步器(AQS)。
所以,个人认为,锁在对于需要同步的代码块比较大的情况下,锁比同步关键字效率高,因为锁是自己唤醒等待线程的(唤醒等待比唤醒阻塞代价小,一个是线程内调用,一个是系统调用)。同时,对于多个资源的并发同步,同步关键字一次只能锁一个对象,多次锁的话,会存在死锁问题(同步关键字的锁是否会延伸到引用的对象,个人猜测估计是不会的,那么在并发情况里,对于共享资源引用共享资源还是要很谨慎的),因为同步关键字锁的是资源,那么锁是更有优势的,因为它管理的是线程。
除此之外,同步关键字比及锁的灵活性更是相差甚远,当然不用手动释放(尤其多次重入),简单方便也可以说是同步关键字的优点了,所以,锁同时也更适合在很复杂,对性能要求更高的场景里使用。
一切的一切,都在我的预料之中,所以我一直认为这些同步工具都是用来处理多线程并发的,然后我错了。
无论是对象原生的wait/notify机制,还是锁内部的condition通信机制,更别提,同步屏障,循环屏障,信号量,以及线程间数据传输工具这些同步工具了,所以并发工具的引用还有相当大一部分作用在于多线程之间的协同工作(一般称为线程间通信)。
提到这里,流下了没技术的眼泪,因为多线程协作这种高大上的操作几乎没用到过,除了最最简单的异步方法调用。后来想一想,其实也不是我的错,我们接触的场景大部分都是单次io(操作数据库,或者操作微服务),或者对于多次io的调用顺序有极高的要求(数据库顺序很重要,微服务的话有一部分无直接顺序关系),所以没用到。那么,在于多io,顺序要求不高的场景下,开启异步调用真的是很完美的操作。
例如,我去饭店吃饭,向一个服务员要餐具,向另一个服务员让上食物,两者都齐备了(多线程状态的同步),我开始操作,是不是效率会高那么一丢丢,比起只有一个服务员,先拿餐具,再端饭菜(不考虑制作时间成本)。
最后的最后,提及线程池,就是为了防止线程数的野蛮增长的,有控制的使用多线程,保护自己的同时也顺带提高了可用性(线程是需要内存开销的,多线程的调度也会给cpu带来代价)。
网友评论