Java多线程-13 (锁的优化)

本文介绍了Java并发编程中锁优化的几种策略,包括减少锁持有时间、减小锁粒度、使用读写锁、锁分离和锁粗化。同时,也探讨了JVM对锁的优化,如锁偏向和轻量级锁,以及乐观锁和悲观锁的概念。这些优化方法能够提升系统的并发性能和效率。
摘要由CSDN通过智能技术生成

                                                          锁的优化

                                                                                            个人博客:www.xiaobeigua.icu 


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

1.1.1减少锁持有时间

        对于使用锁进行并发控制的应用程序来说,如果单个线程特有锁的时间过长,会导致锁的竞争更加激烈,会影响系统的性能。

        在程序中需要尽可能减少线程对锁的持有时间,如下面代码:

public synchronized void syncMethod(){

        othercode1();

        mutexMethod();

        othercode2();
}

        在syncMethod同步方法中,假设只有mutexMethod()方法是需要同步的, othercode1()方法与 othercode2()方法不需要进行同步。如果 othercode1 与 othercode2 这两个方法需要花费较长的 CPU 时间,在并发量较大的情况下,这种同步方案会导致等待线程的大量增加。一个 较好的优化方案是,只在必要时进行同步,可以减少锁的持有时间,提高系统的吞吐量。

如把上面的代码改为:

public void syncMethod(){

    othercode1();
    
    synchronized (this) {
        mutexMethod();
    }

    othercode();
}

        只对 mutexMethod()方法进行同步,这种减少锁持有时间有助于降 低锁冲突的可能性,提升系统的并发能力。

1.1.2 减小锁的粒度

        一个锁保护的共享数据的数量大小称为锁的粒度。

        如果一个锁保护的共享数据的数量大就称该锁的粒度粗,否则称该锁的粒度细。锁的粒度过粗会导致线程在申请锁时需要进行不必要的等待。减少锁粒度是一种削弱多线程锁竞争的一种手段,可以提高系统的并发性。

         在JDK7前,java.util.concurrent.ConcurrentHashMap类采用分段锁协议,可以提高程序的并发性。

        

1.1.3 使用读写分离锁代替独占锁

        使用ReadWriteLock读写分离锁可以提高系统性能,使用读写分离锁也是减小锁粒度的一种特殊情况。第二条建议是能分割数据结构实现减小锁的粒度,那么读写锁是对系统功能点的分割。

        在多数情况下都允许多个线程同时读,在写的使用采用独占锁,在读多写少的情况下,使用读写锁可以大大提高系统的并发能力。

        具体详细例子可以看看之前写的读写锁文章: Lock锁之ReentrantReadWriteLock 读写锁

1.1.4 锁分离

        将读写锁的思想进一步延伸就是锁分离。读写锁是根据读写操作功能上的不同进行了锁分离。根据应用程序功能的特点,也可以对独占锁进行分离。

        如 java.util.concurrent.LinkedBlockingQueue 类中 take() put()方法分别从队头取数据,把数据添加到队尾,虽然这两个方法都是对队列进行修改操作,由于操作的主体是链表,take()操作的是链表的头部,put()操作的是链表的尾部,两者并不冲突。如果采用独占锁的话,这两个操作不能同时并发,在该类中就采用锁分离,take()取数据有读取锁,put()添加数据时有自己的添加锁,这样 take()与 put()相互独立实现了并发 。

                

1.1.5 粗锁化

        为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短。但是凡事都有一个度,如果对同一个锁不断的进行请求,同步和释放,也会消耗系统资源。

如:

public void method1(){

    synchronized( lock ){
        
        同步代码块 1
    }

    synchronized( lock ){

        同步代码块 2
    }
}

        JVM 在遇到一连串不断对同一个锁进行请求和释放操作时,会把所有的锁整合成对锁的一次请求,从而减少对锁的请求次数,这个操作叫锁的粗化。

如上一段代码会整合为:

public void method1(){

    synchronized( lock ){

        同步代码块 1
        同步代码块 2
    }
}

        在开发过程中,也应该有意识的在合理的场合进行锁的粗化,尤其 在循环体内请求锁时 如:

for(int i = 0 ; i< 100; i++){

    synchronized(lock){}
}

        这种情况下,意味着每次循环都需要申请锁和释放锁,所以一种更 合理的做法就是在循环外请求一次锁。如:

synchronized( lock ){

    for(int i = 0 ; i< 100; i++){}
}

1.2  JVM 对锁的优化

1.2.1 锁偏向

        锁偏向是一种针对加锁操作的优化,如果一个线程获得了锁,那么锁就进入偏向模式,当这个线程再次请求锁时,无须再做任何同步操作,这样可以节省有关锁申请的时间,提高了程序的性能。

        锁偏向在没有锁竞争的场合可以有较好的优化效果,对于锁竞争比较激烈的场景,效果不佳, 锁竞争激烈的情况下可能是每次都是不同的线程来请求锁,这时偏向模式就失效了。

1.2.2 轻量级锁

        如果锁偏向失败,JVM 不会立即挂起线程,还会使用一种称为轻量级锁的优化手段。

        会将对象的头部作为指针,指向持有锁的线程堆栈内部,来判断一个线程是否持有对象锁。如果线程获得轻量级锁成功,就进入临界区。如果获得轻量级锁失败,表示其他线程抢到了锁,那么 当前线程的锁的请求就膨胀为重量级锁。当前线程就转到阻塞队列中变为阻塞状态。

        偏向锁,轻量级锁都是乐观锁,重量级锁是悲观锁

        一个对象刚开始实例化时,没有任何线程访问它,它是可偏向的,即它乐观的认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程。

         偏向第一个线程,这个线程在修改对象头成为偏向锁时使用 CAS 操作,将对象头中 ThreadId 改成自己的 ID,之后再访问这个对象时,只需要对比 ID 即可。 一旦有第二个线程访问该对象,因为偏向锁不会主动释放,所以第二个线程可以查看对象的偏向状态,当第二个线程访问对象时,表示在这个对象上已经存在竞争了,检查原来持有对象锁的线程是否存活,如果挂了则将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行原来线程的 栈,检查该对象的使用情况,如果仍然需要偏向锁,则偏向锁升级为轻量级锁。

        轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对同 一个锁的操作会错开,或者稍微等待一下(自旋)另外一个线程就会释放锁, 当自旋超过一定次数,或者一个线程持有锁,一个线程在自旋,又来第三个线程访问时, 轻量级锁会膨胀为重量级锁,重量级锁除了持有锁的线程外,其他的线程都阻塞。

1.3 补充:乐观锁 悲观锁

        乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题。

乐观锁:

        乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。

悲观锁:

        悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。

 

乐观锁的实现方式  主要有两种:CAS机制和版本号机制,乐观锁本身是不加锁的,只是在更新时判断一下数据是否被其他线程更新了;AtomicInteger便是一个例子。

悲观锁的实现方式  是加锁,加锁既可以是对代码块加锁(如Java的synchronized关键字),也可以是对数据加锁(如MySQL中的排它锁)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值