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 }