• ReentraneLock & synchronized & AQS


    sychronized  (monitor监视器)  -- 自旋获取锁形式

      把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility)。原子性意味着一个线程一次只能执行由一个指定监控对象(lock)保护的代码,从而防止多个线程在更新共享状态时相互冲突。可见性则更为微妙;它要对付内存缓存和编译器优化的各种反常行为。

      monitorenter  & monitorexit

    ReentranLock -- 阻塞获取锁形式

      Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

      reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

       ReentrantLock 构造器的一个参数是 boolean 值,它允许您选择想要一个 公平(fair)锁,还是一个 不公平(unfair)锁。公平锁使线程按照请求锁的顺序依次获得锁;而不公平锁则允许讨价还价,在这种情况下,线程有时可以比先请求锁的其他线程先得到锁。在现实中,公平保证了锁是非常健壮的锁,有很大的性能成本。要确保公平所需要的记帐(bookkeeping)和同步,就意味着被争夺的公平锁要比不公平锁的吞吐率更低。作为默认设置,应当把公平设置为 false ,除非公平对您的算法至关重要,需要严格按照线程排队的顺序对其进行服务。

    在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。

    ReentrantLock之lock形式  -- for (;;){} 循环获取锁 -- 自旋

      1、根据构造器创建lock,设定为公平锁or不公平锁

      2、lock,获取锁

        添加至Node队列,包含上Node地址,下Node地址,以及线程信息

        获取锁队列,从head位开始,尝试去获取锁   

          获取方式:获取当前线程信息,得到锁当前状态,对处于state为0的,通过CAS操作,成功后将当前线程设置至可执行线程ExclusiveOwnerThread 。

                如果失败,调用Thread的interrupted(),中断线程

      3、unlock

        更改状态,将ExclusiveOwnerThread 置为空

    ReentrantLock扩展的功能

       实现可轮询的锁请求 

    在内部锁中,死锁是致命的——唯一的恢复方法是重新启动程序,唯一的预防方法是在构建程序时不要出错。而可轮询的锁获取模式具有更完善的错误恢复机制,可以规避死锁的发生。 
    如果你不能获得所有需要的锁,那么使用可轮询的获取方式使你能够重新拿到控制权,它会释放你已经获得的这些锁,然后再重新尝试。可轮询的锁获取模式,由tryLock()方法实现。此方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值true。如果锁不可用,则此方法将立即返回值false。此方法的典型使用语句如下: 

      

    Lock lock = ...;   
    if (lock.tryLock()) {   
    try {   
    // manipulate protected state   
    } finally {   
    lock.unlock();   
    }   
    } else {   
    // perform alternative actions   
    }   
    

      

    实现可定时的锁请求 

       当使用内部锁时,一旦开始请求,锁就不能停止了,所以内部锁给实现具有时限的活动带来了风险。为了解决这一问题,可以使用定时锁。当具有时限的活 

       动调用了阻塞方法,定时锁能够在时间预算内设定相应的超时。如果活动在期待的时间内没能获得结果,定时锁能使程序提前返回。可定时的锁获取模式,由tryLock(long, TimeUnit)方法实现。 

      只在时间范围内去获取锁,超出时间则认为无法获取锁。

     实现可中断的锁获取请求 

      可中断的锁获取操作允许在可取消的活动中使用。lockInterruptibly()方法能够使你获得锁的时候响应中断。

    ReentrantLock不好与需要注意的地方

    (1) lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放
    (2) 当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源。 Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。
     
     
    Condition -- 条件变量
      条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。
     
      每一个Lock可以有任意数据的Condition对象,Condition是与Lock绑定的,所以就有Lock的公平性特性:如果是公平锁,线程为按照FIFO的顺序从Condition.await中释放,如果是非公平锁,那么后续的锁竞争就不保证FIFO顺序了。
      await() 操作实际上就是释放锁,然后挂起线程,一旦条件满足就被唤醒,再次获取锁;
      signal() 就是唤醒Condition队列中的第一个非CANCELLED节点线程;
      signalAll() 就是唤醒所有非CANCELLED节点线程。当然了遇到CANCELLED线程就需要将其从FIFO队列中剔除。

      完整的await()操作是安装如下步骤进行的:

      1. 将当前线程加入Condition锁队列。特别说明的是,这里不同于AQS的队列,这里进入的是Condition的FIFO队列。进行2。
      2. 释放锁。这里可以看到将锁释放了,否则别的线程就无法拿到锁而发生死锁。进行3。
      3. 自旋(while)挂起,直到被唤醒或者超时或者CACELLED等。进行4。
      4. 获取锁(acquireQueued)。并将自己从Condition的FIFO队列中释放,表明自己不再需要锁(我已经拿到锁了)

    FIFO(固定长度队列)

    AQS的全称为(AbstractQueuedSynchronizer)

      AQS是链表,有一个head引用来指向链表的头节点,AQS在初始化的时候head、tail都是null,在运行时来回移动。此时,我们最少至少知道AQS是一个基于状态(state)的链表管理方式。

      列的结构就变成了以下这种情况了,通过这样的方式,就可以让执行完的节点释放掉内存区域,而不是无限制增长队列,也就真正形成FIFO了。

      AQS获取锁是通过tryAcquire去获取的,本身并没有获取方式,释放锁是release,通过tryRelease。

     

    原理:

      ReentraneLock 是Lock的一种实现,也是java层次锁的体现,而非synchronized语言特性的形式。ReentraneLock最基本的核心在于AQS,AbstractQuenedSynchronizer提供了一系列的模板方法,

    通过将lock和unlock操作分别交给子类去实现,AQS提供了公平锁和非公平锁2种模式。而ReentraneLock是使用的Sync,是AQS的一个子类,使用的是非公平锁。

      AQS会把请求线程放入一个CLH队列中,由线程去尝试获取锁,如果成功,则将当前运行线程设置为该线程,并将state状态从0设置为1,再重入则会继续加1。如果线程尝试去获取锁的时候,发现已经有

    线程将锁独占,这时AQS会将线程包装为一个Node放入CLH队列的tail处,然后对线程进行阻塞。在阻塞前会再次的请求获取锁,如果还是没有获取到锁,则将线程阻塞。

    CLH队列的含义:CLH为队列锁,队列中的Node对标注为是否需要获取锁。

  • 相关阅读:
    Codeforces 877 C. Slava and tanks
    Codeforces 877 D. Olya and Energy Drinks
    2017 10.25 NOIP模拟赛
    2017 国庆湖南 Day1
    UVA 12113 Overlapping Squares
    学大伟业 国庆Day2
    51nod 1629 B君的圆锥
    51nod 1381 硬币游戏
    [JSOI2010]满汉全席
    学大伟业 2017 国庆 Day1
  • 原文地址:https://www.cnblogs.com/binbang/p/6425248.html
Copyright © 2020-2023  润新知