zoukankan      html  css  js  c++  java
  • ReentrantLock与synchronized

    ReentrantLock和synchronized同样都是用于多线程同步,它们在功能上有相近之处,但通常而言,ReentrantLock可以用于替代synchronized。

    1, ReentrantLock具备synchronized功能

     1 static Object monitor = new Object();
     2 synchronized(monitor) {
     3     //执行代码
     4 }
     5 
     6 //创建一个重入锁,并且产生一个条件监视器对象
     7 static ReentrantLock lock = new ReentrantLock();
     8 static Condition monitor = lock.newCondition();
     9 lock.lock();
    10 //执行代码
    11 lock.unlock();

      可以注意到,ReentrantLock有显示锁对象,锁对象可以由用户决定请求锁和释放锁的时机,它们甚至可以不在同一个代码块中,而synchronized并没有这么灵活。      

           synchronized使用的是Object对象内置的监视器,通过Object.wait/Object.notify()等方法对当前线程做等待和唤醒操作。synchronized只能有一个监视器,如果调用监视器的notifyAll,那么会唤醒所有线程,较为不灵活。

           ReentrantLock使用的是条件监视器Condition,通过ReentrantLock.newCondition()方法来获取。同一个ReentrantLock可以创建多个condition实例。每个Condition维护有自己的等待线程waiter队列,调用signalAll只会唤醒自己队列内的线程。与Object.wait()/Object.notify()的使用方式一样,Condition调用await()/signal()系列方法来达到同样的目的。

           监视器的使用需要注意两点:

    1)       监视器的wait和notify操作会改变线程在等待队列里的状态,这个状态是所有线程可见的,必须保证线程安全,所以一定要有锁支撑。也就是说,调用wait/notify类型的方法时,必须在该监视器观察的锁内部执行。

    2)       监视器的notify方法并不会直接唤醒线程,它只会改变线程在等待队列里的状态,真正的唤醒操作是抽象队列同步器(AQS)完成的,

    2, ReentrantLock更灵活

    ReentrantLock的灵活性体现在以下几个方面

      ReentrantLock可以指定公平锁或非公平锁,而synchronized限制为公平锁。ReentrantLock默认为非公平锁。

      ReentrantLock的条件监视器较之synchronized更加方便灵活。ObjectMonitor的等待队列个数仅有一个,而Condition支持多个队列。ObjectMonitor释放锁进入wait或wait timeout状态,必须响应中断,Condition可以不响应中断。

    3, 概括比较synchronized和ReentrantLock优劣

      ReentrantLock获取锁和释放锁的操作更加灵活,且具备独立的条件监视器,等待和唤醒线程的操作也更加方便和多样化,在多线程环境下,ReentrantLock的执行效率比synchronized高。

      但是,synchronized的存在还是有意义的,程序不仅仅是执行执行更快的操作和更灵活的就会更优秀,还要考虑到维护成本,synchronized具有完备的语义,一个获得锁操作就一定会对应一个释放锁操作,否则就会有编译期异常出现,对于语法友好来讲,synchronized可维护性更高。

    4, ReentrantLock的条件监视器

      Condition,即条件,这个类在AQS里起到的是监视器monitor的作用,监视器是用于监控一段同步的代码块,可以用于线程的阻塞和解除阻塞。

      每当条件监视器增加一个等待线程的时候,该线程也会进入一个条件等待队列,下次signal方法调用的时候,会从队列里获取节点,挨个唤醒。

    Condition核心方法:

      a)       await():当前线程进入等待状态,直到响应通知SIGNAL或者中断Interrupt。

      b)       awaitUninterruptily():当前线程进入等待状态,知道响应通知SIGNAL。

      c)        awaitNanos(long):指定一个纳秒为单位的超时时长,当前线程进入等待状态,直到响应通知、中断或者超时,其返回值为剩余时间,小于0则超时。

      d)       awaitUntil(Date):制定一个超时时刻,当前线程进入等待状态,知道响应通知、中断或者超时

      e)       signal/signalAll:对condition队列中的线程进行唤醒/唤醒全部

    从这些方法可以看出condition方法共分为两类:

      1)       await:等效于Object.wait。

      2)       signal:等效于Object.notify。

      wait和notify是Object提供的native方法,Condition为了与Object的方法区分而另行命名的。

      以AQS的Condition实现类ConditionObject为例,ConditionObject维护了一个双向waiter队列,下面两个属性记录了它的首尾节点。

    1 //条件队列头结点
    2 private transient Node firstWaiter;
    3 //条件队列尾结点
    4 private transient Node lastWaiter;

      Node节点对象为一个双向链表节点,其数据域为线程的引用。

    await方法的实现

     1 public final void await() throws InterruptedException {
     2     //如果当前线程是中断状态,那么抛出中断异常
     3     if(Thread.interrupted()) {
     4         throw new InterruptedException();
     5     }
     6     //把当前线程添加到waiter队列尾
     7     Node node = addConditionWaiter();
     8     //释放当前节点拥有的锁,因为后面还要添加锁,不释放会造成死锁
     9     long savedState = fullyRelease(node);
    10     int interruptMode = 0;
    11     while(!isOnSyncQuequ(node)) {
    12         LockSupport.park(this);
    13         if((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
    14             break;
    15         }
    16     }
    17     if(acquireQueued(node, savedState) && interruptMode != THROW_IE) {
    18         interruptMode = REINTERRUPT;
    19     }
    20     if(node.nextWaiter != null) {
    21         //clean up if cancelled
    22         unlinkCancelledWaiters();
    23     }
    24     if(interruptMode != 0) {
    25         reportInteruptAfterWait(interruptMode);
    26     }
    27 }

      需要注意的是,阻塞当前线程使用的方法为LockSupport.park(),如果需要唤醒,那么需要有signal()方法来调用LockSupport.unpark(Thread);

    signal方法的实现

           signal方法用于唤醒Condition等待队列中的下一个等待节点

     

     1 public final void signal() {
     2     //只有独占模式才能使用signal,否则抛出异常
     3     if(!isHeldExclusively()) {
     4         throw new IllegalMoinitorStateException();
     5     }
     6     Node first = firstWaiter;
     7     if(first != null) {
     8         doSignal(first);
     9     }
    10 }
    11 private void doSignal(Node first) {
    12     //从等待队列中移除节点,并尝试唤醒节点
    13     do {
    14         if(firstWaiter = first.nextWaiter == null) {
    15             lastWaiter = null;
    16         }
    17         first.nextWaiter = null;
    18     }
    19     while(!transferForSignal(first) && (first = firstWaiter) != null);
    20 }
    21 final boolean transferForSignal(Node node) {
    22     //如果设置waitStatus失败,那么说明节点在signal之前被取消了,此时返回false
    23     if(!compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    24         return false;
    25     }
    26     //这个队列放到sync队列的尾部
    27     Node p = enq(node);
    28     //获取入队节点的前驱节点状态
    29     int ws = p.waitStatus;
    30     //如果前驱节点取消了,那么可以直接唤醒当前节点的线程
    31     //如果前驱结点没有取消,那么设置当前节点为SIGNAL,而不是唤醒这个线程
    32     if(ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) {
    33         LockSupport.unpark(node.thread);
    34     }
    35     return true;
    36 }
  • 相关阅读:
    [国家集训队] Crash 的文明世界
    [国家集训队] middle
    [正睿集训2021] 构造专练
    [正睿集训2021] LIS
    CF482E ELCA
    UVA
    UVA
    UVA
    UVA
    UVA
  • 原文地址:https://www.cnblogs.com/guanghe/p/13473740.html
Copyright © 2011-2022 走看看