JAVA并发之ReentrantLock原理解析

JAVA从版本5开始,在
java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个 。但是这并不意味着我们可以抛弃synchronized关键字了 。
在这些锁实现之前,如果我们想实现锁的功能,只能通过synchronized关键字,例子如下:
private static volatile int value;public static void main() {Runnable run = new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {increaseBySync();}}};Thread t1 = new Thread(run);Thread t2 = new Thread(run);t1.start();t2.start();while (Thread.activeCount() > 1) {Thread.yield();}System.out.println(value);}private static synchronized int increaseBySync() {return value++;}有了ReentrantLock之后,我们可以这样写:
private static volatile int value;private static Lock lock = new ReentrantLock();public static void main(String[] args) {testIncreaseWithLock();}public static void main() {Runnable run = new Runnable() {@Overridepublic void run() {try {lock.lock();for (int i = 0; i < 1000; i++) {value++;}} finally {lock.unlock();}}};Thread t1 = new Thread(run);Thread t2 = new Thread(run);t1.start();t2.start();while (Thread.activeCount() > 1) {Thread.yield();}System.out.println(value);}以上两段代码都可以实现value值同步自增1,并且value都能得到正确的值2000 。下面我们就来分析下ReentrantLock有哪些功能以及它底层的原理 。
可重入锁从名字我们就可以看出ReentrantLock是可重入锁 。可重入锁是指当某个线程已经获得了该锁时,再次调用lock()方法可以再次立即获得该锁 。
举个例子,当某个线程在执行methodA()时,假设已经获得了锁,这是当它执行到methodB()时可以立即获得methodB里面的锁,因为两个方法是调用的同一把锁 。
private static Lock lock = new ReentrantLock();public static void methodA(){try{lock.lock();//dosomethingmethodB();}finally{lock.unlock();}}public static void methodB(){try{lock.lock();//dosomthing}finally{lock.unlock();}}synchronized也是可重入锁,有兴趣的同学可以自己写个例子测试下 。
公平锁通过源码我们可以看到ReentrantLock支持公平锁,并且默认是非公平锁 。
//ReentrantLock源码public ReentrantLock() {sync = new NonfairSync();}//ReentrantLock源码public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}下面是非公平锁和公平锁的lock()方法实现,从两个lock()方法我们可以看到,它们的不同点是在调用非公平锁的lock()方法时,当前线程会尝试去获取锁,如果获取失败了则调用acquire(1)方法入队列等待;而调用公平锁的lock()方法当前线程会直接入队列等待(acquire方法涉及到AQS下面会讲到) 。
//ReentrantLock源码,非公平锁final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}//ReentrantLock源码,公平锁final void lock() {acquire(1);}而synchronized是一个非公平锁 。
超时机制ReentrantLock还提供了超时机制,当调用tryLock()方法,当前线程如果获取锁失败会立刻返回;而当调用带参tryLock()方法是,当前线程如果在设置的timeout时间内未获得锁,也会立刻返回 。而这些功能背后主要是依赖AQS实现的 。
//ReentrantLock源码public boolean tryLock() {return sync.nonfairTryAcquire(1);}//ReentrantLock源码public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));}synchronized没有这个功能 。
可被中断ReentrantLock有一个lockInterruptibly()方法,它会最终调用AQS的两个方法:
AQS方法一中如果当前线程被中断则抛出InterruptedException,否则尝试去获取锁,获取成功则返回,获取失败则调用aqs方法二doAcquireInterruptibly() 。
AQS方法二中在for循环线程自旋中也会判断当前线程是否被标记为中断,如果是也会抛出InterruptedException 。
doAcquireInterruptibly()的细节我们在下面讲解AQS的时候会重点介绍,它和doAcquire()方法很类似,唯一区别是会抛出InterruptedException 。
//lock方法public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}//aqs方法一public final void acquireInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (!tryAcquire(arg))doAcquireInterruptibly(arg);}//aqs方法二private void doAcquireInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.EXCLUSIVE);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}


推荐阅读