锁的优化及注意事项

有助于提高锁性能的建议

  • 减少锁的持有时间:只在必要时进行同步
  • 减少锁的粒度:减少锁定对象的范围(分割数据结构),从而减少锁冲突的可能。如concurrentHashMap 默认分为16个SEGMENT,。但是减少粒度会引入一个新的问题, 当系统需要取得全局锁时,消耗的资源会比较多,如ConcurrentHashMap.size() (size()先使用无锁求和,如果失败才会尝试全部SEGMENT加锁)。
  • 读写分离锁来替换独占锁:(分割系统功能点)在读多 写少的场合,读写锁对系统性能是很有好处的。
  • 锁分离:如 LinkedBlockingQueue。
  • 锁粗化:如果对一个锁不停地进行请求、同步和释放,其本生会消耗系统性能,反而不利于性能的优化。(锁粗化和减少锁的持有时间是相反的,在不同的场合,它们的效果不同,应根据实际情况进行权衡)

 

 

JVM 对锁优化的努力

  • 锁偏向:若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,无需进行相关的同步操作,从而节省了操作时间。如果在此之前有其他线程进行了锁请求,则拥有者需要释放偏向锁。因此,对于几乎没有锁竞争的场合,偏向锁有较好的优化效果,而对于竞争激烈的场合,可能每次都是不同的线程请求相同的锁,使偏向锁失效。使用JVM参数 -XX:+UseBiasedLocking 可以开启偏向锁。
  • 轻量级锁:简单地将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁,如果失败就会膨胀为重量级锁。
  • 自旋锁:由于当前线程暂时无法获得锁,虚拟机会让线程做几个空的循环,经过若干次循环后,还不能获得锁,才会在操作系统层面挂起。
  • 锁消除:JVM在JIT编译时,通过上下文的扫描,去除不可能存在共享资源竞争的锁(可能使用一些JDK内置API,如StringBufer、Vector),可以节省毫无意义的请求锁的时间。锁消除涉及的关键技术为逃逸分析,观察某一个变量是否会逃出某个作用域。逃逸分析必须在-server模式下进行,使用-XX:+DoEscapeAnalysis参数打开逃逸分析,-XX:+EliminateLocks参数可以打开锁消除。

 

 

ThreadLocal

为每个线程分配不同的对象,需要在应用层面保证,ThreadLocal只是起到了容器的作用。当前线程Thread有 ThreadLocal.ThreadLocalMap threadLocals = null,设置ThreadLocal中的数据,实际是向线程的Map中以key=当前ThreadLocal对象,value=https://my.oschina.net/u/3171491/blog/需要的值 写入。

Thread类内部维护这些变量,这意味着只要线程不退出,这些引用将一直存在。当线程退出时,Thread类会进行一些清理工作,包括ThreadLocalMap, 在线程退出前调用exi()。因此使用线程池时,如果希望及时回收对象,最好使用ThreadLocal.remove()方法将变量移除。

 

 

无锁

锁是一种悲观的策略,总是假设每次的临界区操作会发生冲突。而无锁是一种悲观的策略,它会假设对资源的访问是无冲突的,如果遇到则再处理。

比较交换:CAS

与锁相比,CAS会使程序更加复杂。但由于非阻塞性,它对死锁问题天生免疫,并且线程间的影响远远比基于锁的方式要小。使用无锁的方式完全没有锁竞争带来的系统消耗。

CAS(V,E,N):V表示要更新的值,E表示期待的值,N表示新值。仅当V=E时,才会将V的值设置为N,如果过V != E,则说明V的值已经被其他线程更新了。最后,返回当前V的真实值。当多个线程同时使用CAS操作变量时,只有一个会胜出,其他均会失败。失败的线程不会挂起,仅是被告知失败,并且允许再次尝试,也允许失败放弃。

如果当前线程将得到的值V1通过算法计算出新的值V2,然后通过CAS更新失败后(其他线程将V1更新为V3),还要再次拿到其他线程更新的新值V3再次通过算法计算出V3,循环进行上述操作直至成功?

无锁的线程安全API:atomic包

AtomicInteger:

AtomicReference:

AtomicStampedReference:

AtomicIntegerArray:

AtomaticIntegerFieldUpadter:

 

 

 

 

 

 

赞 (0) 评论 分享 ()