如何优化锁

并发性能问题的根源

线程切换的系统开销

  1. 操作系统保存和恢复上下文
  2. 调度器进行线程调度
  3. 处理器高速缓存重新加载
  4. 可能导致整个高速缓存区被冲刷,从而带来时间开销

锁优化思路

多线程对锁资源的竞争会引起上下文切换,锁竞争导致的线程阻塞越多,上下文切换就越频繁,系统的性能开销就越大
• 在多线程编程中,锁本身不是性能开销的根源,锁竞争才是性能开销的根源
锁优化归根到底是减少竞争

减少锁的持有时间

锁的持有时间越长,意味着越多的线程在等待该竞争锁释放
优化方法:将一些与锁无关的代码移出同步代码块,尤其是那些开销较大的操作以及可能被阻塞的操作

减少锁粒度

锁分离
读写锁实现了锁分离,由读锁和写锁两个锁实现,可以共享读,但只有一个写
• 读写锁在多线程读写时,读读不互斥,读写互斥,写写互斥
• 传统的独占锁在多线程读写时,读读互斥,读写互斥,写写互斥
在读远大于写的多线程场景中,锁分离避免了高并发读情况下的资源竞争,从而避免了上下文切换

锁分段

在使用锁来保证集合或者大对象的原子性时,可以将锁对象进一步分解
Java 1.8之前的ConcurrentHashMap就是用了锁分段

非阻塞乐观锁代替竞争锁

volatile
• volatile关键字的作用是保证可见性和有序性,volatile的读写操作不会导致上下文切换,开销较小
• 由于volatile关键字没有锁的排它性,因此不能保证操作变量的原子性(++),只能在使用得当的情况下一定程度上保证原子性。

CAS
• CAS是一个原子的if-then-act操作
• CAS是一个无锁算法实现,保障了对一个共享变量读写操作的一致性
• CAS不会导致上下文切换,Java的Atomic包就使用了CAS算法来更新数据,而不需要额外加锁

synchronized锁优化

在JDK 1.6中,JVM将synchronized同步锁分为偏向锁、轻量级锁、自旋锁、重量级锁
JIT编译器在动态编译同步代码块时,也会通过锁消除、锁粗化的方式来优化synchronized同步锁

wait/notify优化

通过Object对象的wait、notify、notifyAll来实现线程间的通信,例如生产者-消费者模型。
通过并发包中的condition来实现等同于wait、notify的功能。
借助并发包中提供的lockSupport工具合理控制线程休眠时间

合理的线程池大小

线程池的线程数量不宜过大
一旦线程池的工作线程总数超过系统所拥有的处理器数量,就会导致过多的上下文切换

猜你喜欢

转载自blog.csdn.net/weixin_38370441/article/details/115142179