娜娜的美好一天▲如何自如权衡线程安全与性能,程序员:时刻保持对代码的思考( 二 )


娜娜的美好一天▲如何自如权衡线程安全与性能,程序员:时刻保持对代码的思考
文章图片
娜娜的美好一天▲如何自如权衡线程安全与性能,程序员:时刻保持对代码的思考
文章图片
简单来说就是甲拿着A锁 , 获取B锁 , 乙拿着B锁获取A锁 , 注意在甲乙获得第一个锁的时候休眠会儿 , 来制造死锁 。 解决方法:保证加锁的顺序性 。 设定为锁定AB的时候 , 总是先锁小的再锁大的 。 可以通过甲乙的唯一ID活着通过System自带的获得ID函数System.identityHashCode()publicclassSafeOperateimplementsITransfer{privatestaticObjecttieLock=newObject()//加时赛锁@Overridepublicvoidtransfer(UserAccountfrom,UserAccountto,intamount)intfromHash=System.identityHashCode(from)inttoHash=System.identityHashCode(to)//或者你可以保证ID唯一可以用ID实现//先锁hash小的那个if(fromHash&lttoHash){synchronized(from){System.out.println(Thread.currentThread().getName()+"get"+from.getName())Thread.sleep(100)synchronized(to){System.out.println(Thread.currentThread().getName()+"get"+to.getName())from.flyMoney(amount)to.addMoney(amount)}}elseif(toHash&ltfromHash){}else{synchronized(tieLock){//那个线程拿到再处理synchronized(from){synchronized(to){to.addMoney(amount)通过tryLock核心思路就是while死循环获得两个锁 , 都获得才可以进行操作然后break 。 Randomr=newRandom()while(true){if(from.getLock().tryLock()){System.out.println(Thread.currentThread().getName()+"get"+from.getName())if(to.getLock().tryLock()){System.out.println(Thread.currentThread().getName()+"get"+to.getName())//两把锁都拿到了}finally{to.getLock().unlock()from.getLock().unlock()SleepTools.ms(r.nextInt(10))//防止发生活锁!尝试拿锁的机制中 , 发生多个线程之间互相谦让 , 不断发生拿锁 , 释放锁的过程 。 比如上面的活锁代码while循环如果没有时间休眠的话 , 由于JDK线程获得锁是谦让式获得的 , 可能出现如下:甲拿到A尝试拿B , 拿B失败了再重新尝试拿A , 再重新拿B , 这样周而复始的尝试 。 乙拿到B尝试拿A , 拿A失败了再重新尝试拿B , 再重新拿A , 这样周而复始的尝试 。 解决办法:把对象加锁顺序的不确定性变成确定性的顺序 。 解决:通过内在排序 , 保证加锁的顺序性通过尝试拿锁配合休眠若干也可以 。 饥饿:线程因无法访问所需资源而无法执行下去的情况 。 不患寡 , 而患不均 , 如果线程优先级“不均” , 在CPU繁忙的情况下 , 优先级低的线程得到执行的机会很小 , 就可能发生线程饥饿;持有锁的线程 , 如果执行的时间过长 , 也可能导致饥饿问题 。 保证资源充足公平的分配资源防止持有锁的线程长时间执行 。 多线程是好但是要切记勿装逼强行使用 , 装逼必被打 。 我们使用多线程的出发点是要了解 , 是为了提供系统的性能 , 充分利用系统资源 。 但是引入多线程后会引入额外的开销 。 衡量应用程序性能一般:服务时间、延迟时间、吞吐量、可伸缩性 , 深入了解性能优化 。 做应用的时候:先保证程序的正确性跟健壮性 , 确实达不到性能要求再想如何提速 。 一定要以测试为基准 。 一个程序中串行的部分永远是有的.装逼利器:阿姆达尔定律S=1/(1-a+a/n)系统中某一部件因为采用更快的实现后 , 整个系统性能的提高与该部分的使用频率或者在总运行时间中比例有关 。 直观地 , 你把一个部件提升了很多 , 但是这个部件却不经常使用 , 因此这种提高看上去是提高其实并没有 。 所以Amdahl定律认为我们除了需要关注部件的加速比 , 还要关注该部件的使用频率/情况 。 影响因素多线程的上下文切换 , 不是多线程一定好 。 比如只有一个核心 , 让你做语数外三门作业 , 如果你顺序做是可以的 , 这样不会涉及到任何作业场景的布置环境切换 , 而如果你非要同时做三门作业那么就会来回切换了 , 反正耗时!联想到多线程的上下文切换同样如此 , CPU切换一个上下文就是几微妙哦!线程池的设置思想跟这个类似 。 内存同步加锁等操作在编译代码后都有叠加指令存在的 。 一些线程获得锁失败了还会进行阻塞式的等待 。 常用的思路一般也是如下几点 。 缩小锁的范围能用方法块尽量不要锁函数privateMap&ltString,String&gtmatchMap=newHashMap&lt&gt()publicsynchronizedbooleanisMatch(Stringname,Stringregexp){//太大Stringkey="user."+nameStringjob=matchMap.get(key)job=matchMap.get(key)if(job==null){returnfalsereturnPattern.matches(regexp,job)publicbooleanisMatchReduce(Stringname,Stringregexp){Stringjobsynchronized(this){//细致化更好synchronized特性:可重入 , 独享 , 悲观锁锁优化:锁消除是发生在编译器级别的一种锁优化方式 , 是指虚拟机即时编译器在运行时 , 对一些代码上要求同步 , 但是被检测到不可能存在共享数据竞争的锁进行削除(开启锁消除的参数:-xx:+DoEscapeAnalysis-XX:+EliminateLocks)锁粗化是指有些情况下我们反而希望把很多次锁的请求合并成一个请求 , 以降低短时间内大量锁请求、同步、释放带来的性能损耗 。 减少锁的粒度跟锁分段使用锁的时候 , 锁保护对象锁是多个的 , 多个之间其实是独立变化的 , 那就用多个锁来分别保护 。 但是要注意发生死锁 。 publicclassFinenessLock{publicfinalSet&ltString&gtusers=newHashSet&ltString&gt()publicfinalSet&ltString&gtqueries=newHashSet&ltString&gt()publicvoidaddUser(Stringu){synchronized(users){//注意锁的谁users.add(u)publicvoidaddQuery(Stringq){synchronized(queries){//注意锁的谁queries.add(q)比如我们的ConcurrentHashMap用的分段锁来提速 。 替换独占锁


推荐阅读