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

1.减少锁持有的时间

对于使用锁进行并发控制的应用程序而言,在锁的竞争过程中,单个线程对锁的持有时间与系统性能有着直接的关系。如果线程持有锁的时间越长,那么相对的,锁的竞争也就越激烈。比如,要求100个人填写自己的个人信息,但是只有1支笔,每个人拿着笔的时间都很长,那么总体花费的时间也就很长。如果真的只有一支笔,那么最好让每个人花尽可能少的时间持笔,务必做到想好了再拿笔写,而不能拿着笔思考该怎么想。程序开发也是类似的,应尽可能地减少对某个锁的持有时间,以减少线程间互斥的可能。比如在以下代码中

public synchronized void syncMethod(){
        othercode1();
        mutextMethod();
        othercode2();
    }

在上述方法中,othercode1和othercode2都是重量级方法,需要花费较长的CPU时间,但是只有mutextMethod方法是有同步需要的。在并发量较大的时候,使用这种对整个方法做同步的方法,则会导致等待线程大量增加。因为一个线程,在进入该方法时,需要获得内部锁,只有在整个任务执行完以后,才会释放锁。
一个较为优化的方法是,只在必要的时候进行同步,这样能够明显减少锁的持有时间,提高系统的吞吐量。

public void syncMethod(){
        othercode1();
        synchronized (this){
            mutextMethod();
        }
        othercode2();
    }

在上述代码中,只针对需要同步的方法使用内部锁加锁,锁占用的时间相对较短,因此有更高的并行度。

2.减少锁的粒度

将单个独占锁变为多个锁,从而将加速请求均分到多个锁上,有效降低对锁的竞争。但是,增加锁的前提是多线程访问的变量间相互独立,如果多线程需要同时访问多个变量,则很难进行锁分解,因为要维持原子性。

3.用读写分离锁来代替独占锁

使用读写分离锁ReadWriteLock可以提高系统的性能。使用读写分离锁来替代独占锁是减小锁粒度的一种特殊情况。如果说减小锁粒度是通过分割数据结构实现的,那么读写分离锁则是针对系统功能点的分割。
在读多写少的场合,使用读写分离锁可以有效提升系统的并发能力。

4.锁分离

如果将读写锁的思想进一步延申,就是锁分离。读写锁根据读写操作功能上的不同,进行了有效的锁分离。依据应用程序的特点,使用类似的分离思想,也可以对独占锁进行分离。典型的案例就是LinkedBlockingQueue的实现。
在LinkedBlockingQueue的实现中,take函数和put函数分别实现了从队列中取数据和往队列中增加数据的功能。虽然两个函数都是对当前队列进行了修改操作,但是LinkedBlockingQueue是基于链表的,因此两个操作分别用于队列的前端和尾端,从理论上讲,并不冲突。
如果使用独占锁,则要求在两个操作进行时分别获取当前队列的独占锁,那么take和put方法就不能真正的并发,在运行时,它们会彼此等待对方释放锁资源。这种情况下,锁的竞争会比较激烈,从而影响并发时的性能。
因此,在JDK实现中,并没有采用这种方式,取而代之的时用两把不同的锁分离了take和put方法。

 private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

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

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

以上代码片段定义了takeLock和putLock,分别在take和put方法中使用。
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();  //不能有两个线程同时进行put方法
        try {
            /* 如果队列已经满了,则等待*/
            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方法可以取数据了
    }

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();
            }
            x = dequeue(); //取数据
            c = count.getAndDecrement(); //数量减1,原子操作,因为会和put函数同时访问count,变量c时cout减1前的值
            if (c > 1)
                notEmpty.signal(); //通知其他take方法
        } finally {
            takeLock.unlock(); //释放锁
        }
        if (c == capacity)
            signalNotFull();  //通知put已有空余空间
        return x;
    }

5.锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早地获得资源执行任务。但是,凡事都有一个度,如果对同一个锁不停地进行请求,同步和释放,其本身也会消耗宝贵地资源,反而不利于优化。
为此,虚拟机在遇到一连串连续对同一个锁不断进行请求和释放操作时,便会把所有地锁操作整合成对锁地一次请求,从而减少对锁的请求同步次数,这个操作叫锁粗化。

 public void syncMethod(){
        synchronized (lock){
            //do sth
        }
//做其他不需要同步的工作,但不会消耗很长的cpu执行时间
        synchronized (lock){
            //do sth
        }
    }

上述代码会被整合成如下形式:

public void syncMethod(){
//整合成一次锁请求
        synchronized (lock){
            //do sth
            //做其他不需要同步的工作,但不会消耗很长的cpu执行时间
        }
    }

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容