JavaAPI-Lock-简介

目录

1. 目标

2. JDK版本

3. interface Lock

3.1. what

3.2. 差异对比

4. ReentrantLock可重入排他锁

4.1. 比synchronized的优势

4.2. 如何选择synchronized和reentrantlock

4.3. 参数true,所谓的公平锁

4.4. 锁实现

5. ReentrantReadWriteLock

6. 如何选择Lock和同步器

7. 自旋锁


1. 目标

基于源码,简要介绍concurrent包的Lock以及主要实现类,对比synchronized关键字等。

2. JDK版本

JDK1.8

3. interface Lock

3.1. what

Lock是控制多线程访问共享资源的工具。通常其实现应该是排他的,即同一时刻只有一个线程可访问共享资源。然而,有些实现是允许并发访问共享资源的,例如ReadWriteLock。

3.2. 差异对比

与synchronized(方法和变量)相比,差异如下

  • lock作为接口,具有多种实现,可以进行自定义
    • 请求lock时是否允许中断/超时,依据具体实现的特性而定,如执行特性,顺序保证,或其他实现特性。
    • 所有的特性都应该doc写明。
  • lock的实现将提供更多的行为和语义
    • 加锁行为:尝试加锁trylock、可中断加锁、超时加锁
    • 其他可提供的:顺序保证、是否可重入、是否共享、死锁检查等
  • 允许更加灵活的结构,例如具有不同的属性,具有多个关联的condition对象等
  • 获取锁与释放锁的范围scope和顺序order差异
    • synchronized提供访问[与对象关联的隐示监视器锁]的能力。
      其要求锁的获取与释放必须发生在块结构中:
      1当获取多个锁后,释放必须是反序的;
      2获取和释放必须在同一个范围scope内;
    • synchronized的范围规则使得编程更简单,可以规避使用lock引起的错误。
      但是这种范围规则的限制,正是lock发挥[提供更灵活的使用方式]机会。
      例如在并发遍历访问数据时,需要一个一个或是链式的加锁:
      1 lock a, lock b
      2 unlock a, lock c
      3 unlock b, lock d
      Lock的实现之所以满足这种场景,是因为lock的获取与释放没有范围scope和顺序order限制。
  • synchronized会引起线程上下文切换的资源浪费  关于这点我看了jvm的源码,发现objectMonitor内部实现用的也是 自旋+cas+park,所以无法证明 关键字比lock造成更多的线程上下文切换。
  • 使用方式差异
    • synchronized用于方法声明或变量
      synchronized void m1(){//todo}
      synchronized(变量){//todo}
    • lock丢弃了结构化加锁,从而引起锁无法自动释放,所以需要进行unlock。
      lock使用习惯:使用try-catch-finally
      lock.lock();
      try{
      //TODO
      }finally{
      lock.unlock();
      }
  • 实现方式差异
    • synchronized是使用对象的隐示监视器锁
    • lock需自行实现锁机制,基于AQS,unsafe.cas

4. ReentrantLock可重入排他锁

4.1. 比synchronized的优势

与synchronized具有相同行为和语义的重入排他锁,但是提供了更多的行为。

  • lock 尝试一次获取锁,成功则返回,否则内部循环获取,且不会中断,因为中断被内部处理
  • trylock 尝试一次获取锁,返回成功或失败
  • lockInterruptibly 尝试一次获取锁,成功则返回,否则循环获取,且可中断,因为中断则抛出异常
  • unlock
  • getHoldCount 当前线程重入次数
  • hasQueuedThread 参数线程是否在等待该锁
  • hasQueuedThreads 是否有线程在等待锁
  • isLocked 锁是否被占用

4.2. 如何选择synchronized和reentrantlock

  • 从行为和语义多样性、使用灵活性、具体场景要求考虑

4.3. 参数true,所谓的公平锁

  • 倾向于将锁分配给等待时间较长的线程,但是不保证确切的访问顺序。
  • 多个线程访问公平锁时性能很低(与默认的非公平锁相比),具有很小的好处,即较少饥饿线程。
  • 不保证线程调度的公平性
    • 与其他没有执行的或没有成功获取锁的线程相比,已经获取锁的线程可多次重复获取;
    • 不可预期的发生取消、中断、超时,先来的线程可能没拿到锁,但后来的线程可能拿到锁。
  • trylock是忽略公平性参数的,只要锁是可获取的,他就会返回

4.4. 锁实现

  • 继承AQS,且使用state来表示获取锁的重入次数;使用cas操作state
  • acquire方法
    • tryacquire 成功则返回
      循环获取锁

  • trylock会立即尝试占用锁,如果成功则占用,否则立即返回,不会进入等待队列。
  • 锁获取流程
    • 非公平锁 NonfairSync
      • lock方法 //差异一
            if CASstate(0,1)  
        	    表示获取锁
            else 
        	    acquire(1)
        
        tryacquire方法 //差异二
        	if state == 0
        		if CASstate(0,x) 
        			获取锁
        	else if 当前线程==持有锁的线程
        			进行重入操作
        		

    • 公平锁 FairSync
      • lock方法 //差异一
        	acquire(1)
        
        tryacquire方法 //差异二
        	if state == 0
        		if 等待队列中无其他节点 && CASstate(0,x) 
        			获取锁
        	else if 当前线程==持有锁的线程
        			进行重入操作
        		

5. ReentrantReadWriteLock

与重入锁具有类似的语义

仍然使用aqs的state作为占用计数,但是:
使用高16位表示读锁占用,计数运算为state cas c+65536(1<<16 即为第17位为1,低16为0),state>>16表示读锁占用个数。
使用低16位标示写锁占用,计数运算为state cas c+A,state低16位即为写锁占用次数。
这样就可使用state&65535((1<<16)-1 即低16位全为1)来判断是否写占用。

特性

  • 获取的顺序性
    • 不是利用reader或writer的请求顺序,而是提供一种可选的公平性。
    • 非公平模式
      • 默认选择
      • 非公平锁会被不断的竞争,不断的cas,虽然会不定期的延缓读或写线程,但是比公平锁的性能高。
    • 公平模式
      • 使用类似到达顺序的规则,即检查等待队列
      • 当一个lock被释放时
        • 有个写线程等待的时间最长,则其获得锁
        • 有一组读线程的等待时间比写线程等待时间长,则该组读线程获得锁
      • 当读线程lock时,需等待之前的写线程获得锁并释放;当写线程lock时,需等待之前的读线程获得锁并释放。
      • 当读写线程进行trylock时 ,会立即尝试占用,如果成功则获得锁,否则立即返回。
  • 重入性
    • 写线程可以重复请求写锁,在获得写锁时也可获取读锁;
    • 读线程可以重复请求读锁,但必须等待写锁释放,且获得读锁后不可能获取写锁;
  • 锁降级
    • 顺序为:获取写锁后,写操作,然后再获得读锁,然后释放写锁,读操作,然后释放读锁。
    • 目的:写之后,我就想立马使用,不想再去等待读锁。
    • 只允许从写到读,但是无法从读到写锁。
  • 写锁是提供condition的,而读锁不提供且会抛异常。
  • 使用举例
    • 使用降级特性,写后立即读;
    • 改善非线程安全的集合的并发性,如读时获取读锁,写时获取写锁。

6. 如何选择Lock和同步器

同步关键字的jvm实现已经修改,不应把导致更多上线文切换资源浪费作为选择依据。

应考虑两者的差异、使用场景,参见上文,如表现形式、行为与语义、用法。

7. 自旋锁

在AQS类中可以看到自旋锁的实现方式,基本操作包括 循环(参与获得锁)、node状态对比、cas(获取锁)、park(停止线程调度) 、try-catch等。AQS依赖自旋锁,以实现行为多样性,让Lock具有更多的特性,所以自旋锁是java lock的具体实现基础。个人理解,讨论为什么使用自旋锁,其就是要讨论lock的特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值