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 }

 

posted @ 2020-08-11 09:42  光何  阅读(600)  评论(0编辑  收藏  举报