VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • Java并发编程之重入锁(ReentrantLock)

简介

ReentrantLock重入锁,由于它的加锁和解锁操作需要手动来完成,所以也称为显式锁

ReentrantLock是基于AQS独占模式实现的独占锁,同时只能有一个线程能获取到该锁,获取不到锁的线程将被放入该锁的AQS阻塞队列中等待。

ReentrantLock的简单使用

一个ReentrantLock使用示例,对临界资源进行加锁,当线程访问临界资源时需要先获得锁,如果锁已经被其他线程所持有,则需要等待其他线程释放锁后再重新获取锁,直到获取锁之后才能继续执行。


Copy
public class ReentrantLockDemo { public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); Runnable runnable = () -> { try{ reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "获得了锁"); Thread.sleep(3000); }catch (InterruptedException ex){ ex.printStackTrace(); }finally { reentrantLock.unlock(); System.out.println(Thread.currentThread().getName() + "释放了锁"); } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); } } /* * 输出结果: * Thread-1获得了锁 * Thread-1释放了锁 * Thread-0获得了锁 * Thread-0释放了锁 */

可重入性

可重入性是指线程获得锁之后,没有释放锁,当再次需要获得同一把锁的时候仍然可以成功。如果锁是不可重入的,第二次获得锁的时候就会导致死锁。

state变量记录了锁的重入次数,每次lock()方法的调用会使state加1,每次unlock()方法的调用会使state减1,当state为0时锁被释放,将锁的持有者设置为null。

如下示例,多次调用lock()方法是可以的,但是对应的unlock()方法也要调用对应次数。


Copy
public class ReentrantLockDemo { public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); Runnable runnable = () -> { try{ reentrantLock.lock(); reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "获得了锁"); Thread.sleep(3000); }catch (InterruptedException ex){ ex.printStackTrace(); }finally { reentrantLock.unlock(); reentrantLock.unlock(); System.out.println(Thread.currentThread().getName() + "释放了锁"); } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); } } /* * 输出结果: * Thread-1获得了锁 * Thread-1释放了锁 * Thread-0获得了锁 * Thread-0释放了锁 */

当unlock()方法次数少于lock()方法调用次数时,state变量值大于0,相当于当前线程还持有该锁,其他线程依然不能获取到锁,只能继续等待。如下所示:


Copy
public class ReentrantLockDemo { public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); Runnable runnable = () -> { try{ reentrantLock.lock(); reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "获得了锁"); Thread.sleep(3000); }catch (InterruptedException ex){ ex.printStackTrace(); }finally { reentrantLock.unlock(); System.out.println(Thread.currentThread().getName() + "释放了锁"); } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); } } /* * 输出结果: * Thread-1获得了锁 * Thread-1释放了锁 */

当unlock()方法次数大于lock()方法调用次数时,相当于锁已经被释放,锁的持有者不再是当前线程,此时再次调用unlock()方法将抛出IllegalMonitorStateException异常。


Copy
public class ReentrantLockDemo { public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); Runnable runnable = () -> { try{ reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "获得了锁"); Thread.sleep(3000); }catch (InterruptedException ex){ ex.printStackTrace(); }finally { reentrantLock.unlock(); reentrantLock.unlock(); System.out.println(Thread.currentThread().getName() + "释放了锁"); } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); } }

输出结果:


Copy
Thread-0获得了锁 Thread-1获得了锁 Exception in thread "Thread-0" java.lang.IllegalMonitorStateException at java.base/java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:149) at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1302) at java.base/java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:439) at com.buhe.demo.demos.reen.ReentrantLockDemo.lambda$main$0(ReentrantLockDemo.java:18) at java.base/java.lang.Thread.run(Thread.java:834) Exception in thread "Thread-1" java.lang.IllegalMonitorStateException at java.base/java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:149) at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1302) at java.base/java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:439) at com.buhe.demo.demos.reen.ReentrantLockDemo.lambda$main$0(ReentrantLockDemo.java:18) at java.base/java.lang.Thread.run(Thread.java:834)

公平策略

使用无参构造函数创建的ReentrantLock默认是非公平锁,带参数的构造函数可以指定公平策略,true表示公平锁,false表示非公平锁。

公平锁是指先请求锁的线程一定先获得锁,反之非公平锁则是随机的。

示例:


Copy
public class ReentrantLockDemo { public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); Runnable runnable = () -> { for(int i = 0; i < 2; i++) { try { reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "获得了锁"); Thread.sleep(3000); } catch (InterruptedException ex) { ex.printStackTrace(); } finally { reentrantLock.unlock(); } } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); Thread thread3 = new Thread(runnable); Thread thread4 = new Thread(runnable); Thread thread5 = new Thread(runnable); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); } }

锁的获取是随机的,即使刚释放锁的线程也有可能立即获得锁。

输出结果:


Copy
Thread-0获得了锁 Thread-0获得了锁 Thread-1获得了锁 Thread-1获得了锁 Thread-2获得了锁 Thread-2获得了锁 Thread-3获得了锁 Thread-4获得了锁 Thread-4获得了锁 Thread-3获得了锁

修改ReentrantLock的创建方式,改为公平锁


Copy
ReentrantLock reentrantLock = new ReentrantLock(true);

先请求锁的线程一定先获得锁,锁的获取是有序的。

输出结果:


Copy
Thread-2获得了锁 Thread-1获得了锁 Thread-0获得了锁 Thread-3获得了锁 Thread-4获得了锁 Thread-2获得了锁 Thread-1获得了锁 Thread-0获得了锁 Thread-3获得了锁 Thread-4获得了锁

使用公平锁不会产生饥饿现象,由于要维护线程的顺序,使公平锁的效率低下,所以没特殊情况应该使用非公平锁。

和synchronized的比较

synchronized和ReentrantLock都属于独占锁,两者性能相差不大,但是在使用上,ReentrantLock要更灵活。

  • synchronized的加锁和解锁是自动进行的,易于操作,但不够灵活。ReentrantLock的加锁和解锁是手动进行的,不易操作,但更灵活。

  • ReentrantLock支持公平锁和非公平锁,而synchronized只支持非公平锁。

  • ReentrantLock可以关联多个条件队列,而synchronized最多只能关联一个条件队列。

  • 在等待锁的过程中,使用ReentrantLock能使等待的线程响应中断,而使用synchronized时,线程只能保持等待直到获取到锁后继续执行。

  • ReentrantLock支持锁申请等待超时,当在给定的时间内还没有获取到锁,则主动放弃获取锁。

  • ReentrantLock支持尝试获取锁,如果锁没有被其他线程占用,则获得锁成功并立即返回true,如果锁被其他线程所占用,则锁获取失败直接返回false,不会进行等待。

  • ReentrantLock和synchronized都是可重入锁。

源码分析

ReentrantLock和AQS的关系如下图:

ReentrantLock的内部类Sync继承自AQS,它的子类NonfairSync和FairSync分别实现了非公平锁和公平锁策略。

通过ReentrantLock的构造函数参数来指定公平策略:


Copy
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }

下面以非公平锁为例分析下加锁和解锁的过程。

加锁

非公平锁加锁调用的是NonfairSync的lock方法,默认state为0,通过CAS将state设置为1,设置成功则表示加锁成功,并设置当前线程为锁的持有者。设置失败则调用AQS的acquire方法。


Copy
//java.util.concurrent.locks.ReentrantLock.NonfairSync#lock final void lock() { //CAS设置状态值 if (compareAndSetState(0, 1)) //设置当前线程为锁的持有者 setExclusiveOwnerThread(Thread.currentThread()); else //调用AQS的acquire方法 acquire(1); }

acquire方法,当tryAcquire方法返回false时,会把当前线程放入AQS阻塞队列。


Copy
//java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

tryAcquire方法由AQS的子类实现,这里就是Sync以及它的子类来实现。

tryAcquire方法中会判断state值,如果state为0,则会尝试获取锁,使用CAS设置state的值,设置成功返回true,设置失败返回false。

如果state大于0,则判断持有锁的线程是不是当前线程,如果是则需要更新重入次数,重新设置state的值后返回true,反之则表示锁已经被其他线程所持有,直接返回false。


Copy
//java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }

Copy
//java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //获取锁的状态值 int c = getState(); //判断锁的状态,为0则表示锁没有被任何线程所持有 if (c == 0) { //CAS设置状态值 if (compareAndSetState(0, acquires)) { /设置当前线程为锁的持有者 setExclusiveOwnerThread(current); return true; } } //判断持有锁的线程是否为当前线程 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
解锁

非公平锁和公平锁的解锁过程是一致的,解锁unlock方法调用的是AQS的release方法,release方法会调用tryRelease方法尝试释放锁,释放锁成功之后的处理由AQS完成。同tryAcquire方法,tryRelease方法也是需要AQS子类实现的。


Copy
//java.util.concurrent.locks.AbstractQueuedSynchronizer#release public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }

Copy
//java.util.concurrent.locks.ReentrantLock.Sync#tryRelease protected final boolean tryRelease(int releases) { //减少重入次数 int c = getState() - releases; //如果不是锁的持有者调用unlock方法则抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //如果重入次数为0,则清空锁的持有线程 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }

如果当前持有锁的线程不是当前线程则直接抛出异常,否则继续判断state的值,如果state为0,则清空锁的持有线程,如果state不为0,则减少重入次数。

 

原文:https://www.cnblogs.com/seve/p/14663940.html


相关教程