Java|伙计,提高自己的并发技能,从锁优化开始!( 二 )

method1()method2()两个方法的耗时长 , 那么会导致整个程序的执行时间变长 。 因此我们可以选择下面这样优化:
这样做的好处就是 , 只针对mainMethod()方法做了同步控制 , 锁占用的时间相对较短 , 因此能够有较高的并发度 。 较少锁的持有时间有助于降低锁冲突的可能性 , 进而提升系统的并发能力 。
减小锁粒度减小锁粒度也是一种削弱多线程锁竞争的有效手段 。 这种技术典型的使用场景就是ConcurrentHashMap类的实现 。 对ConcurrentHashMap有所了解的小伙伴应该知道 , 传统的HashTable之所以是线程安全的就是因为它是对整个方法加锁 。 而ConcurrentHashMap的性能比较高是因为它内部细分了若干个小的HashMap , 称之为段(SEGMENT) 。 在默认情况下 , 一个ConcurrentHashMap类可以细分为16个端 , 性能相当于提升了16倍 。
ConcurrentHashMap中增加一个数据 , 并不是对整个HashMap加锁 , 而是首先根据hashcode得出应该被存放在哪个段中 , 然后对该段加锁 , 并完成put()操作 。 当多个线程进行put()操作的时候 , 如果锁的不是同一个段 , 那么就可以实现并行操作 。
但是 , 减小锁粒度会带来一个新的问题:当系统需要取得全局锁时 , 其消耗的资源会比较多 。 例如:当ConcurrentHashMap调用size()方法时 , 需要或者所有子段的锁 。 虽然事实上 , size()方法会先使用无锁的方式求和 , 如果失败才会尝试这种方式 , 但是在高并发的情况下 , ConcurrentHashMap的性能依然要弱于同步的HashMap
减小锁粒度 , 就是指缩小锁定对象的范围 , 从而降低锁冲突的可能性 , 进而提高系统的并发能力
用读写锁来替换独占锁读写分离锁可以有效地帮助减少锁竞争 , 提高系统性能 。 比如:A1、A2、A3三个线程进行写操作 , B1、B2、B3三个线程进行读操作 , 如果使用重入锁或者内部锁 , 那么所有读之间 , 读与写之间 , 写之间都是串行操作 。 但是因为读操作并不会造成数据的完整性破坏 , 因此这种等待是不合理的 。
因此可以使用读写分离锁ReadWriteLock来提高系统的性能 。 使用示例如下:



锁粗化通常情况下 , 为了保证多线程间的有效并发 , 会要求每个线程持有锁的时间尽量短 , 在使用完公共资源后 , 应该立即释放锁 , 只有这样 , 等待在这个锁上的其他线程才能尽早地获得资源执行任务 。
错误示例:
优化后:
尤其是在循环中要注意锁的粗化
错误示例:
优化后:
JVM进行的锁优化


偏向锁锁偏向是一种针对加锁操作的优化手段 。 核心思想:如果一个线程获得了一个锁 , 那么这个锁就进入了***偏向模式*** , 当这个线程释放完这个锁后 , 下次同其他线程再次请求时 , 无须在做任何同步操作 。 这样就节省了大量的锁申请相关操作 。
但是在锁竞争比较激烈的场合 , 效果不佳 , 因为在竞争激烈的场合 , 最有可能的情况就是每次都是不同的线程来请求 , 这样偏向模式会失效 , 因此还不如不启用偏向锁 。 可以通过 JVM参数 -XX:+UseBiasedLocking开启偏向锁 。
轻量级锁如果偏向锁失败 , 那么虚拟机并不会立即挂起线程 , 它还会使用一种称为轻量级锁的优化手段 。 轻量级锁的操作也很方便 , 它只是简单地将对象头部作为指针指向持有锁的线程堆栈的头部 , 来判断一个线程是否持有对象锁 。 如果线程获得轻量级锁成功 , 则可以顺利进入临界区 , 如果轻量级锁加锁失败 , 则表示其他线程抢先争夺到了锁 , 那么当前线程的锁请求就会膨胀为重量级锁 。


推荐阅读