zoukankan      html  css  js  c++  java
  • ReentrantLock-公平锁、非公平锁、互斥锁、自旋锁

      重入锁,又称递归锁,是指在同一线程中,外部方法获取锁后,内层递归方法仍然可以获取该锁。如果锁不具备重入性,那么当一个线程两次获取锁的时候就会发生死锁。java提供了java.util.concurrent.ReentrantLock来解决重入锁问题。

      ReentrantLock重入锁并不是容器集合类的一部分,但它在Concurrency包中占据了非常重要的一部分。在并发容器的实现中被大量使用。

      ReentrantLock是一种显式锁,与synchronized隐式锁对应。synchronized不能显式的对Lock对象进行操作,因此有很多不便利性。而显式锁提供了多种方法来操作Lock。

      1)       lock():获取锁,如果锁不可用,那么当前线程会休眠直到获取锁为止。

      2)       lockInterruptibly():可中断地获取锁,如果当前线程发生interrupt,则释放锁。

      3)       tryLock():尝试获取锁,如果取到了,那么返回true,它与lock()的区别在于它不会休眠当前线程。

      4)       unlock():释放锁。

      5)       newCondition():创建一个当前锁的条件监视器Condition,condition实例用于控制当前Lock的线程队列的notify和wait。

      ReentrantLock的实现基于AQS,通过tryAcquire和tryRelease的重写,实现了锁机制和重入机制。

    1,ReentrantLock的公平锁与非公平锁

      ReentrantLock在底层有两种实现方式,分别是FairSync(公平锁)和NonfairSync(非公平锁),它们lock()流程如图:

     

    FairSync

    1 static final class FairSync extends Sync {
    2     private static final long serialVersionUID = -300003432432432L;
    3     final void lock() {
    4         //FairSync直接调用acquire方法来获取锁
    5         acquire(1);
    6     }
    7     protected final boolean tryAcqure(int acquires) {...}
    8 }

      FairSync与NonfairSync都会调用同样的acquire方法,因此有必要了解一下acquire方法的实现:

    1 public final void acquire(int arg) {
    2     //只需要注意tryAcquire()方法,它用于请求锁,返回true时后续的操作不再被处理
    3     if(!tryAcquire(arg) && acquireQueued(addWaiter(Node, EXCLUSIVE), arg)) {
    4         selfInterrupt();
    5     }
    6 }

           tryAcquire用于请求锁,当请求失败的时候,会把当前线程加入等待队列,addWaiter()和acquiredQueued()方法分别对应封装等待线程节点和请求入队操作。

    NonfairSync

     1 static final class NonfairSync extends Sync {
     2     private static final long serialVesionUID = 4324242432L;
     3     final void lock() {
     4         //验证当前锁状态,如果是0,那么设置1
     5         //状态为0说明没有其他线程持有锁,当前线程可以直接获得锁
     6         //setExclusiveOwnerThread即为了当前排它锁执行所有者线程方法
     7         if(compareAndSetState(0, 1)) {
     8             setExclusiveOwnerThread(Thread.currentThread());
     9         } else {
    10             //状态不为0,则说明其他线程持有锁,执行获取锁的方法acquire
    11             //该方法最终用于获取锁的方法是tryAcquire
    12             acquire();
    13         }
    14         
    15         protected fianl boolean tryAcquire(int acquires) {
    16             return nonfairTryAcquire(acquires);
    17         }
    18     }
    19 }


      1)       NonFairSync类在lock()方法调用的第一时间,直接验证当前锁状态,如果没有其它线程持有锁(锁状态state为0),那么当前线程会持有锁。NonfairSync与fairSync主要区别为:

      2)       NonFairSync类的tryAcquire()方法执行不同,它直接调用了nonfairTryAcquire()方法,nonfairTryAcquire()方法不要求严格按照等待队列的入队顺序获取锁。

    下面来看一下FairSync.tryAcquire()和NonFairSync.nonfairTryAcquire():

     1 protected final boolean tryAcquire(int acquires) {
     2     final Thread current = Thread.currentThread();
     3     int c = getState();
     4     if(c == 0) {
     5         //注意这个hasQueuedPredecessors()方法,只有FairSync才会调用它
     6         //它是FairSync和NonFairSync仅有的区别
     7         if(!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
     8             setExclusiveOwnerThread(current);
     9             return true;
    10         } else if(current == getExclusiveOwnerThread()) {...}
    11         return false;
    12     }
    13 }

      1)       FairSync保证了FIFO,先入队的等待线程会先获得锁,而NonfairSync任由各个等待线程竞争。

      2)       由于FairSync要保证有序性,所以NonfairSync的性能更高,ReentrantLock默认使用NonfairSync。

    2,ReentrantLock的重入性

           加锁有两种基本形式,互斥锁与自旋锁。

           互斥锁(Mutex),通过阻塞线程来进行加锁,中断阻塞来进行解锁。

     1 public class MutexLock {
     2     private AtomicReference<Thread> owner = new AtomicReference<>();
     3     private LinkedList<Thread> list = new LinkedList<>();
     4     public void lock() {
     5         Thread currentThread = Thread.currentThread();
     6         //没有任何线程持有锁时,让当前线程持有锁,反之则加入等待队列并阻塞
     7         if(!owner.compareAndSet(null, currentThread)) {
     8             waiterQueue.add(currentThread);
     9             //LockSupport阻塞当前线程
    10             LockSupport.park();
    11         }
    12     }
    13     public void unlock() {
    14         //如果解锁的线程不是持有锁的线程,那么抛出异常
    15         if(Thread.currentThread() != owner.get()) {
    16             throw new RuntimeException();
    17         }
    18         //等待队列里有内容时,恢复队头线程,更改持有锁的线程,反之则直接释放锁
    19         if(waiterQueue.size() > 0) {
    20             Thread t = waiterQueue.poll();
    21             owner.set(t);
    22             //LockSupport释放指定线程
    23             LockSupport.unpark(t);
    24         } else {
    25             owner.set(null);
    26         }
    27     }
    28 }

      自旋锁(Spin lock),线程保持运行态,用一个循环体不停地判断某个标质量的状态来确定加锁还是解锁,本质上用一段无意义的死循环来阻塞线程运行。

     1 public class SpinLock {
     2     private AtomicReference<Thread> owner = new AtomicReference<>();
     3     public void lock() {
     4         Thread current = Thread.currentThread();
     5         //没有任何线程持有锁时,让当前线程持有锁,反之则利用循环来阻塞
     6         while (!owner.compareAndSet(null, current)) { }
     7     }
     8     public void unlock() {
     9         Thread current = Thread.currentThread();
    10         //释放锁
    11         owner.compareAndSet(current, null);
    12     }
    13 }

           无论哪种实现方式,都回避不了一个问题,那就是在同一个线程中,如果递归地获取相同的锁,都会出现死锁。设想线程A持有了锁,在释放之前,A再次请求加锁,此时由于锁拥有了持有者,于是由于锁拥有了持有者(A自己),于是A被阻塞了。因此需要引入重入锁:       自旋锁(Spin lock),线程保持运行态,用一个循环体不停地判断某个标质量的状态来确定加锁还是解锁,本质上用一段无意义的死循环来阻塞线程运行。

      1)       在线程持有锁的时候,其它线程不能访问上锁的共享资源。

      2)       在线程持有锁的时候,线程本身可以继续访问上锁的共享资源。

      3)       在多次递归访问中,只有当全部访问都结束了,线程才会释放锁。

      由此可以想到一个很直观的解决方式——计数器,对持有锁的线程的每一次访问进行计数,只有当访问次数清空之后,其他线程才能继续访问。

     1 public class MutexLock {
     2     private AtomicReference<Thread> owner = new AtomicReference<>();
     3     private LinkedList<Thread> waiterQueue = new LinkedList<>();
     4     private volatile AtomicInteger state = new AtomicInteger(0);
     5     public void lock() {
     6         Thread currentThread = Thread.currentThread();
     7         //如果请求锁的线程是当前线程
     8         if(owner.get() == currentThread) {
     9             state.incrementAndGet();
    10             return;
    11         }
    12         //没有任何线程持有锁时,让当前线程持有锁,反之则加入等待队列并阻塞
    13         if(!owner.compareAndSet(null, currentThread)) {
    14             waiterQueue.add(currentThread);
    15             //LockSupport阻塞当前线程
    16             LockSupport.park();
    17         }
    18     }
    19     public void unlock() {
    20         //如果解锁的线程不是持有锁的线程,那么抛出异常
    21         if(Thread.currentThread() != owner.get()) {
    22             throw new RuntimeException();
    23         }
    24         //计数器清空之后才能继续之后的操作
    25         if(state.get() > 0) {
    26             state.decrementAndGet();
    27             return;
    28         }
    29         //等待队列里有内容时,释放指定队列,更改持有锁的线程,反之则清空持有锁的线程
    30         if(waiterQueue.size() > 0) {
    31             Thread t = waiterQueue.poll();
    32             owner.set(t);
    33             //LockSupport释放指定线程
    34             LockSupport.unpark(t);
    35         } else {
    36             owner.set(null);
    37         }
    38     }
    39 }

      FairSync.tryAcquire()和NonfairSync.nofairTryAcquire()有重用部分,无论公平锁还是非公平锁,在处理重入上,代码是一致的:

      1)       判断state标量是否为0,如果为0,那么说明没有线程持有该锁,当前线程可以持有锁,返回true;FairSync.tryAcquire()和NonfairSync.nofairTryAcquire()有重用部分,无论公平锁还是非公平锁,在处理重入上,代码是一致的:

      2)       如果state不为0,那么判断当前线程是否为锁持有者。

      3)       如果不是,那么当前线程不能持有锁,返回false;

      4)       如果是,那么当前线程已经持有锁,此时认同线程请求次数增加,state需要增加acquires次,acquires表示新增的请求锁次数。

     1 final boolean nonfairTryAcquire(int acquires) {
     2     final Thread current = Thread.currentThread();
     3     int c = getState();
     4     if(c == 0) {
     5         //...
     6     } else if(current == getExclusiveOwnerThread()) {
     7         int nextc = c + acquires;
     8         if(nextc < 0) {
     9             throw new Error("Maximum lock count exceeded");
    10         }
    11         setState(nextc);
    12         return true;
    13     }
    14 }

           tryRelease()方法有一个整型参数releases形参,用来表示本次释放锁的次数,如果当前线程不是锁持有者,那么说明这是一次非法调用,当state计数归零的时候,调用setExclusiveOwnerThread(null),用来表示没有线程持有锁了,此后锁可以被任意调用。

     1 protected final boolean tryRelease(int releases) {
     2     int c = getState() - releases;
     3     if(Thread.currentThread() != getExclusiveOwnerThread()) {
     4         throw new IllegalMonitorStateException;
     5     }
     6     boolean free = false;
     7     if(c == 0) {
     8         free = true;
     9         setExclusiveOwnerThread(null);
    10     }
    11     setState(c);
    12     return free;
    13 }
  • 相关阅读:
    一个表对应另一个表中多个主键的查询方法(把一个表当成两个表用)
    可以切换数据库的SqlHelper
    win7安装后的用户选择
    如何删除 Windows.old 文件夹
    Windows Server 2008磁盘清理工具
    sqlserver express版PRIMARY 大小不能超过4G
    一交换机,一光猫、一路由器组internet网的方法
    公司部门职责清晰
    IIS下载EXE(拾遗)
    win2008 IIS 7.0中WebDAV
  • 原文地址:https://www.cnblogs.com/guanghe/p/13469924.html
Copyright © 2011-2022 走看看