java 锁

在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。而在Java5当中,提供了锁对象,这些内容主要集中在java.util.concurrent.locks包下,有多种实现(如 synchronized 和 ReentrantLock等 ) 。

  • 相关概念

    • 锁开销:锁占用内存空间、 CPU初始化和销毁锁、获取和释放锁的时间。程序使用的锁越多,相应的锁开销越大
    • 锁粒度:衡量锁保护的数据量大小,通常选择粗粒度的锁(锁的数量少,每个锁保护大量的数据)。
    • 锁竞争:一个进程多线程视图获取另一个进程或线程持有的锁,就会发生锁竞争。当单进程访问受保护的数据时锁开销小,但是当多个进程同时访问时性能很差。因为增大了锁的竞争。相反,使用细粒度的锁(锁数量多,每个锁保护少量的数据)增加了锁的开销但是减少了锁竞争。例如数据库中,锁的粒度有表锁、页锁、行锁、字段锁、字段的一部分锁。
    • 死锁:至少两个任务中的每一个都等待另一个任务持有的锁的情况

  • 锁的种类

    • 可重入锁/递归锁
      在执行对象中所有同步方法不用再次获得锁,synchronized和ReentrantLock都是可重入锁,可重入锁可以在一定程度避免死锁。🌰:
      // 如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。
      synchronized void setA() throws Exception{
          Thread.sleep(1000);
          setB();
      }
      
      synchronized void setB() throws Exception{
          Thread.sleep(1000);
      }
      
    • 公平锁/非公平锁
      公平锁指多个线程按照申请锁的顺序来获取锁,非公平锁就是没有顺序随机获取锁,所以可能会造成优先级反转或者饥饿现象;synchronized就是非公平锁。ReentrantLock可以通过构造参数来决定是非公平锁还是公平锁,默认是非公平锁。非公平锁的吞吐量性能比公平锁好。
    • 独享锁/共享锁
      独享锁是指该锁一次只能被一个线程所持有;共享锁是指该锁可被多个线程所持有。ReentrantLock和synchronized都是独享锁。
    • 互斥锁/读写锁
      互斥锁在Java中的具体实现就是ReentrantLock;读写锁在Java中的具体实现就是ReadWriteLock。
    • 乐观锁/悲观锁
      • 悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。比如Java里面的同步原语synchronized关键字的实现就是悲观锁。
      • 乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(Compare and Swap 比较并交换)实现的。
    • 分段锁
      分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。当需要put元素的时候,并不是对整个HashMap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取HashMap全局信息的时候,就需要获取所有的分段锁才能统计。
    • 偏向锁/轻量级锁/重量级锁
      这三种锁是指锁的状态,并且是针对synchronized。在Java 5通过引入锁升级的机制来实现高效synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
      • 偏向锁是指一段同步代码一直被一个线程访问,那么该线程就会自动获取锁,来降低获取锁的代价。
      • 轻量级锁是指当锁是偏向锁的时候,被另一线程锁访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
      • 重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
    • 自旋锁
      自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

  • Synchronized

    • 概述
      Synchronized是java当中的关键字,它提供了一种独占锁的方式,Synchronized的获取和释放由JVM来实现,不需要显式的释放锁,非常方便。
      public synchronized void test() {
              //代码块
      }
      
    • 特性
      • Synchronized获取不到锁,就会一直阻塞。
      • 如果获取锁的线程进入休眠或者阻塞状态时,除非当前线程异常,否则其它线程获取锁时就会一直等待。
    • 实现原理
      synchronized是悲观锁,在字节码层被映射成两个指令:monitorenter和monitorexit,当一个线程遇到monitorenter指令时,会尝试去获取锁,如果获取成功,锁的数量+1,(因为synchronized是一个可重入锁,需要使用锁计数来判断锁的情况),如果没有获取到锁,就会阻塞;当线程遇到monitorexit指令时,锁计数-1,当计数器为0时,线程释放锁;如果线程遇到异常,虚拟机会释放锁。

  • ReentrantLock

    • 概述
      Lock是一个接口,它的实现类有很多,常用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock),实现思路大同小异。这里我们主要讲解ReentrantLock。
    • 一些方法
      • lock():如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
      • tryLock():如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false
      • tryLock(long timeout,TimeUnitunit)):如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
      • lockInterruptibly():如果获取了锁定立即返回,如果没有获取锁,当前线程处于休眠状态,直到获取锁,或者当前线程被别的线程中断
    • ReentrantLock特性
      • 等待可中断,避免出现死锁的情况。
      • ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好
    • 实现原理
      ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。(CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。)它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。想尽办法避免线程进入内核的阻塞状态是我们去分析和理解锁设计的关键钥匙。

  • 用哪个?

    • synchronized:在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好。
    • ReenTrantLock:用起来会复杂一些。在基本的加锁和解锁上,两者是一样的,所以无特殊情况下,推荐使用synchronized。ReentrantLock的优势在于它更灵活、更强大,增加了轮训、超时、中断等高级功能。

    注:想要更深入了解锁的运行机制及其他知识的可以参考这篇博文:java并发编程 - 专栏文章目录


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值