锁的优化及注意事项(读书笔记)

有助于提高锁性能的几点建议

  • 减少锁的持有时间 真正需要同步加锁的时候在加锁,减少锁的持有时间有助于减低锁冲突的可能性,进而提升系统的并发能力,
  • 减少颗粒度,所谓减少颗粒度就是指缩小锁定对象的范围,从而减少锁冲突的可能性,进而提升系统的并发能力,问题在于类似于size()获取全局信息的方法调用并不频繁时,这种减少颗粒度的方法才能真正意义上提高系统吞吐量.(分割数据结构实现)
  • 读写分离锁替换独占锁,读写锁是对系统功能点的分割 ReadWriteLock 在读多写少的场合,读写锁对系统性能是很有好吃的,因为如果系统在读写数据时均只使用独占锁,那么读操作和写操作间,读操作和读操作间,写操作和写操作间均不能做到真正的并发,并且需要互相等待.而读操作本身不会影响数据的完整性和一致性,因此 理论上讲,在大部分情况下,应该允许多线程同时读,

锁分离

          如果将读写锁的思想做进一步的延伸,就是锁分离,读写锁根据读写操作功能上的不同,进行了有效的锁分离,依据应用程序的功能特点,使用类似的分离思想,也可以对独占锁进行分离,一个典型案例就是java.util.concurrent.LinkedBlockingQueue的实现.

在LinkedBlockingQueue的实现中,take()函数和put()函数分别实现了从队列中取得数据和往队列中增加数据的功能,虽然两个函数都对当前队列进行了修改操作,但由于LinkedBlockingQueue是基于链表的,因此,两个操作分别作用于队列的前端和尾端,从理论上讲 是不冲突的.

如果使用独占锁,则要求两个操作进行时获取当前队列的独占锁,那么take()和put()操作就不可能真正的并发,在运行时,他们会彼此等待对方释放资源,在这种情况下,锁的竞争会相对比较激烈,从而影响程序在高并发时的性能!

因此在JDK的实现中,并没有采用这种方式,取而代之的是两把不同的锁,分离了take()和put()操作.

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();//take锁

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();//生成一个take锁的绑定Condition实例

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();//put锁

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();//put锁

以上的代码片段,定义了takeLock和putLock,他们分别在take()操作和put()操作中使用,因此,take()函数和put()函数就此互相独立,他们之间不存在锁竞争关系,只需要在take()和take()间,put()和put()间分别对takeLock和putLock进行竞争,从而 削弱了锁竞争的可能性.

函数take()的实现下:

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();//加锁,可以响应中断的锁
    try {
        while (count.get() == 0) {//如果没有数据,一直等待
            notEmpty.await();//等待put()操作的通知
        }
        x = dequeue();//取得第一个数据
        c = count.getAndDecrement();//数量减1,原子操作,因为会和put()函数同时访问count.注意:变量c是count减1前的值
        if (c > 1)
            notEmpty.signal();//通知其他take操作
    } finally {
        takeLock.unlock();//释放锁
    }
    if (c == capacity)
        signalNotFull();//通知put()操作,已有空余空间
    return x;
}

方法put()的实现如下:

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // Note: convention in all put/take/etc is to preset local var
    // holding count negative to indicate failure unless set.
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();//加锁,可以相应中断的锁
    try {
        /*
         * Note that count is used in wait guard even though it is
         * not protected by lock. This works because count can
         * only decrease at this point (all other puts are shut
         * out by lock), and we (or some other waiting put) are
         * signalled if it ever changes from capacity. Similarly
         * for all other uses of count in other wait guards.
         */
        while (count.get() == capacity) {//当数据满了时候
            notFull.await();//等待
        }
        enqueue(node);//插入数据
        c = count.getAndIncrement();//更新数据,变量c是count加1前的值
        if (c + 1 < capacity)
            notFull.signal();//有足够空间,通知其他线程
    } finally {
        putLock.unlock();//释放锁
    }
    if (c == 0)
        signalNotEmpty();//插入成功后,通知take操作取数据
}

通过takeLock和putLock两把锁,LinedBlockingQueue实现了取数据和写数据的分离,是两者在真正意义上成为可并发的操作

  • 锁粗化

通常情况下 为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量的短,就是使用完公共资源后,立即释放锁,只有这样,等待爱这个锁上的其他线程才能今早的获得资源执行任务,但是,凡是都要有一个度,如果对用一个锁不停的请求,同步,释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化,

为此,虚拟机在遇到一连串的连续第对同一个锁不断进行请求和释放的操作时,便会把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数,这个操作叫做锁的粗化,

在开发过程中,我们应该有意识地在合适的场合进行锁的粗化,尤其是在循环内请求锁时,

性能优化就是根据运行时的真实情况对各个资源点进行权衡折中的过程,锁粗化的思想和减少所持有时间是相反的,但在不同的场合,他们的效果并不相同,所以大家需要根据实际情况,进行权衡.

时间: 2024-10-22 07:28:54

锁的优化及注意事项(读书笔记)的相关文章

锁的优化及注意事项

"锁"的竞争必然会导致程序的整体性能下降,以下就是为了降低这种辐作用的建议:     1.减小锁持有时间 如果线程持有锁的时间很长,那么相对地,锁的竞争程度也就越激烈.程序开发应该尽可能地减少对某个锁的占有时间,以减少线程间互斥的可能. public synchronized void syncMethod(){ othercode1(); mutextMethod(); othercode2();} 优化后: public void syncMethod2(){ othercode1

重入锁的好搭档:Condition条件(读书笔记)

Condition结构提供了基本方法如下: void await() throws InterruptedException; void awaitUninterruptibly(); long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; boolean awaitUntil(Date

锁的优化和注意事项

锁优化分为代码层面的优化和jvm层面的优化 1. 代码层面的锁优化的思路和方法 一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点. 这里提到的锁优化,是指在阻塞式的情况下,如何让性能不要变得太差.但是再怎么优化,一般来说性能都会比无锁的情况差一点. ReentrantLock中的tryLock,偏向于一种无锁的方式,因为在tryLock判断时,并不会把自己挂起. 锁优化的思路和方法总结一下,有以下几种. 减少锁持有时间 减小锁粒度 锁分离 锁粗化 锁消除 1.1 减少

品悟性能优化 第三章读书笔记

1. 罗敏老师的书里面第三章讲了几个Oracle的性能检测调优的工具,其实很长时间以前我一直也知道,但是自己一直认为获取trace以及进行查看是一件非常复杂和困难的事情, 以至于一直闭着眼睛不去学习. 其实今年初工作中红遇到的一个大问题 如果先看过这本书的话 明显可以更快的定位到问题,而不是苦苦的等同事返回工作岗位再发现问题根源. 1. sql_trace 可以对不同的session以及系统级别创建sql_trace的跟踪,但是因为系统实例级别的话 会严重影响操作系统的性能 而且会产生大量的日志

Java虚拟机对锁优化所做的努力(读书笔记)

锁偏向 是一种加锁操作的优化手段,他的核心思想是:如果一个线程获得了锁,那么就进入偏向模式,当这个线程再次请求锁时,无须在做任何同步操作,因此在几乎没有锁竞争的场合,偏向锁是比较好的优化效果,因为连续多次极有可能是同一个线程请求同一个相同的锁,对于锁竞争比较激烈的场合,其效果不佳,因为竞争激烈的场合,最有可能的情况是每次都是不同的线程来请求相同的锁,这样偏向模式就会失效,使用Java虚拟机参数-XX:+UseBiasedLocking可以开启偏向锁, 轻量级锁 如果偏向锁失败,虚拟机并不会立即挂

《高性能MySQL》读书笔记--优化服务器设置

MySQL有大量可以修改的参数--但不应该随便去修改.通常只需要把基本的项配置正确(大部分情况下只有很少一些参数是真正重要的),应该将更多的时间花在schema的优化.索引,以及查询设计上.在正确地配置了MySQL的基本配置项之后,再花力气去修改其它配置项的收益通常就比较小了. 1.创建MySQL配置文件 建议不要使用操作系统的安装包自带的配置文件,最好从头开始创建一个配置文件.(首先要确定MySQL使用了哪个配置文件!) 2.InnoDB缓冲池(Buffer Pool) 有一个流行的经验法则说

《高性能MySQL》读书笔记--锁、事务、隔离级别 转

1.锁 为什么需要锁?因为数据库要解决并发控制问题.在同一时刻,可能会有多个客户端对表中同一行记录进行操作,比如有的在读取该行数据,其他的尝试去删除它.为了保证数据的一致性,数据库就要对这种并发操作进行控制,因此就有了锁的概念. 1.1锁的分类 从对数据操作的类型(读\写)分 读锁(共享锁):针对同一块数据,多个读操作可以同时进行而不会互相影响. 写锁(排他锁):当前写操作没有完成前,它会阻断其他写锁和读锁. 大多数时候,MySQL锁的内部管理都是透明的. 1.2锁粒度(Lock granula

【读书笔记】《Android应用性能优化最佳实践》

<第一行代码>读书笔记 一.引言 二.读书内容 书名:<Android应用性能优化最佳实践> 作者:罗彧成 (腾讯音乐Android开发总监) 出版社:机械工业出版社 封面: 三.书籍评价 四.个人心得 五.参考文档

《android开发艺术探索》读书笔记(十五)--Android性能优化

接上篇<android开发艺术探索>读书笔记(十四)--JNI和NDK编程 No1: 如果<include>制定了这个id属性,同时被包含的布局文件的根元素也制定了id属性,那么以<include>指定的id属性为准 No2: 绘制优化 1)onDraw中不要创建新的局部对象 2)onDraw方法中不要做耗时的任务 No3: 内存泄露优化 场景一:静态变量导致的内存泄露: 如果静态变量持有了一个Activity,会导致Activity无法及时释放. 解决办法:1使用Ap