• 重入锁----ReentrantLock


    本节主要从下述四个方面介绍重入锁。

     1.什么是重入锁?

     2.为什么要引用重入锁?

     3.重入锁是怎么实现的?

     4.分析java并发包中ReentrantLock。

    什么是重入锁

    重入锁,支持重进入的锁,表示该锁能够支持一个线程对它重复加锁,即线程在获得锁之后再次获取该锁时不会被阻塞。

    为什么要引用重入锁?

    以子类重写父类方法为例:

    Mutix是不支持重入的锁。(代码摘抄自《java并发编程的艺术》)

     1 import java.util.concurrent.TimeUnit;
     2 import java.util.concurrent.locks.*;
     3 
     4 
     5 
     6 public class Mutix implements Lock
     7 {
     8     private static class Sync extends AbstractQueuedSynchronizer{
     9         //是否处于占用状态
    10         protected boolean isHeldExclusively(){
    11             return this.getState()==1;
    12         }
    13         protected boolean tryAcquire(int acquires){
    14             if(compareAndSetState(0,1)){
    15                 setExclusiveOwnerThread(Thread.currentThread());
    16                 return true;
    17             }
    18             return false;
    19             
    20         }
    21         protected boolean tryRelease(int releases){
    22             if(getState()==0)throw new IllegalMonitorStateException();
    23             setExclusiveOwnerThread(null);
    24             setState(0);
    25             
    26             return true;
    27             
    28         }
    29         Condition newCondition(){
    30             return new ConditionObject();
    31         }
    32         
    33     }
    34     private final Sync sync=new Sync();
    35 
    36     @Override
    37     public void lock()
    38     {
    39         // TODO Auto-generated method stub
    40     sync.acquire(1);
    41     }
    42 
    43     @Override
    44     public void lockInterruptibly() throws InterruptedException
    45     {
    46         // TODO Auto-generated method stub
    47 sync.acquireInterruptibly(1);
    48     }
    49 
    50     @Override
    51     public Condition newCondition()
    52     {
    53         // TODO Auto-generated method stub
    54         return sync.newCondition();
    55     }
    56 
    57     @Override
    58     public boolean tryLock()
    59     {
    60         // TODO Auto-generated method stub
    61         return sync.tryAcquire(1);
    62     }
    63 
    64     @Override
    65     public boolean tryLock(long arg0, TimeUnit arg1)
    66             throws InterruptedException
    67     {
    68         // TODO Auto-generated method stub
    69         return sync.tryAcquireNanos(1, arg1.toNanos(arg0));
    70     }
    71 
    72     @Override
    73     public void unlock()
    74     {
    75         // TODO Auto-generated method stub
    76 sync.release(1);
    77     }
    78     public boolean hasQueuedThreads(){
    79         return sync.hasQueuedThreads();
    80     }
    81 
    82 }
    View Code

    分别以支持重入和不支持重入进行测试;

    测试代码如下:

     1 import java.util.concurrent.locks.ReentrantLock;
     2 
     3 
     4 public class TestLock
     5 {
     6     static Mutix lock=new Mutix();
     7 //    static ReentrantLock lock=new ReentrantLock();
     8 
     9     public static class Widget {  
    10         public  void doSomething() {  
    11             lock.lock();
    12             try{
    13             System.out.println("父类Widget,线程名称:" +Thread.currentThread().getName());
    14             }finally{
    15                 lock.unlock();
    16             }
    17         }  
    18     }  
    19       
    20     public static class LoggingWidget extends Widget {  
    21         public  void doSomething() {  
    22             lock.lock();
    23             try{
    24                  System.out.println("子类LoggingWidget: calling doSomething,线程名称:" +Thread.currentThread().getName());
    25             super.doSomething();  
    26             }finally{
    27                 lock.unlock();
    28             }
    29         }  
    30     }  
    31     
    32     
    33 
    34     /**
    35      * @param args
    36      */
    37     public static void main(String[] args)
    38     {
    39         LoggingWidget test=new LoggingWidget();
    40         test.doSomething();
    41     }
    42 
    43 }
    View Code

    当使用ReentrantLock可重入锁时,显示结果如下:

    子类LoggingWidget: calling doSomething,线程名称:main
    父类Widget,线程名称:main

    当使用Mutix不可重入锁时,结果如下:

    子类LoggingWidget: calling doSomething,线程名称:main

    此时程序并没有结束,而是一直在等待父类的锁,通过jstack命令,查看到Main进程处于等待状态。按正常的处理逻辑,子类是允许调用父类的方法,故重入锁在实际应用中还是很重要的。

    重入锁是怎么实现?

    1)线程再次获取锁。需要判断当前获取锁的线程是否为当前占据锁的线程,如果是,则再次获取成功。

    2)锁的最终释放。若线程重复了N次获得了锁,那需要释放N次才能真正释放该锁。线程再次获取锁时,计时器加1,当释放锁时,计数器减1。

    分析java并发包中ReentrantLock。

    1)自定义组合同步器实现锁的获取和释放,默认是以非公平形式获取锁。

     1         final boolean nonfairTryAcquire(int acquires) {
     2             final Thread current = Thread.currentThread();//当前线程
     3             int c = getState();//获取同步状态
     4             if (c == 0) {//如果同步状态为0,表示当前没有线程获取锁
     5                 if (compareAndSetState(0, acquires)) {//通过CAS算法获取锁
     6                     setExclusiveOwnerThread(current);
     7                     return true;
     8                 }
     9             }
    10             else if (current == getExclusiveOwnerThread()) {
    11                 //如果同步状态不为0,表示已经有线程获取了该锁,判断获取锁的线程是否为当前线程,如果时,同步状态加acquires,一般为加1
    12                 int nextc = c + acquires;
    13                 if (nextc < 0) // overflow
    14                     throw new Error("Maximum lock count exceeded");
    15                 setState(nextc);//设置同步状态
    16                 return true;
    17             }
    18             return false;
    19         }
    20 
    21         protected final boolean tryRelease(int releases) {
    22             int c = getState() - releases;//计算当前同步状态减去releases
    23             if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程不等于获取锁的线程
    24                 throw new IllegalMonitorStateException();
    25             boolean free = false;
    26             if (c == 0) {//如果C==0,表示同步状态完全释放,将占用线程设置为Null,并且返回true
    27                 free = true;
    28                 setExclusiveOwnerThread(null);
    29             }
    30             setState(c);
    31             return free;
    32         }
    View Code

    在调用ReentrantLock的lock和unlock方法时,实际是调用同步器中的方法。

     1  public boolean tryLock() {
     2         return sync.nonfairTryAcquire(1);
     3     }
     4 
     5 public boolean tryLock(long timeout, TimeUnit unit)
     6             throws InterruptedException {
     7         return sync.tryAcquireNanos(1, unit.toNanos(timeout));
     8     }
     9  public void unlock() {
    10         sync.release(1);
    11     }
    View Code

    从代码中可以看到,最终调用的是sync中的方法,对于用户而言,sync是透明的。

    2)公平和非公平获取锁。

     1  /**
     2      * Sync object for non-fair locks
     3      */
     4     static final class NonfairSync extends Sync {
     5         private static final long serialVersionUID = 7316153563782823691L;
     6 
     7         /**
     8          * Performs lock.  Try immediate barge, backing up to normal
     9          * acquire on failure.
    10          */
    11         final void lock() {
    12             if (compareAndSetState(0, 1))
    13                 setExclusiveOwnerThread(Thread.currentThread());
    14             else
    15                 acquire(1);
    16         }
    17 
    18         protected final boolean tryAcquire(int acquires) {
    19             return nonfairTryAcquire(acquires);
    20             //直接调用Sync的方法。
    21         }
    22     }
    View Code
     1  static final class FairSync extends Sync {
     2         private static final long serialVersionUID = -3000897897090466540L;
     3 
     4         final void lock() {
     5             acquire(1);
     6         }
     7 
     8         /**
     9          * Fair version of tryAcquire.  Don't grant access unless
    10          * recursive call or no waiters or is first.
    11          */
    12         protected final boolean tryAcquire(int acquires) {
    13             final Thread current = Thread.currentThread();
    14             int c = getState();
    15             if (c == 0) {
    16                 if (!hasQueuedPredecessors() &&
    17                     compareAndSetState(0, acquires)) {
    18                     setExclusiveOwnerThread(current);
    19                     return true;
    20                 }
    21             }
    22             else if (current == getExclusiveOwnerThread()) {
    23                 int nextc = c + acquires;
    24                 if (nextc < 0)
    25                     throw new Error("Maximum lock count exceeded");
    26                 setState(nextc);
    27                 return true;
    28             }
    29             return false;
    30         }
    31     }
    View Code

    公平和非公平最大区别在于:公平锁需要判断当前节点的前一个节点是否为头节点(hasQueuedPredecessors() ),如果是,才允许通过CAS算法获取锁,如果不是,表示有线程比当前线程更早的获取锁,因此需要等待前驱线程获取锁并释放锁之后才能继续获取锁。非公平不需要进行判断。公平锁保证了线程处理之间的公平,通过FIFO,保证了公平性,而代价是进行大量的线程切换。非公平锁虽然有时会造成线程饥饿,但极少的线程切换,保证了更大的吞吐量。

    ReentrantLock锁默认采用的是非公平锁。

    1  public ReentrantLock() {
    2         sync = new NonfairSync();//默认采用非公平锁
    3     }
    View Code

    总结:最近这几天在看《JAVA并发编程的艺术》,作者讲解的很详细,本来不想写博客,网上关于这些的文章已经非常多了,而且该书讲解的那么透彻,但后来想想,在写博客其实也是整理、梳理知识的过程,如果自己都没有弄明白,如何能够写出来。

  • 相关阅读:
    Windows-Redis-x64-5.0.9【感谢大佬】
    Debezium初试
    一键结束进程
    Vscode自动刷新
    从零到一搭建一个jenkins+github持续构建平台
    git项目迁移
    AWS IoT 消息代理
    解析器:request.body、request.POST、request.data
    Unity程序员的Unreal 简明教程(二,模型与材质)
    Unity程序员的Unreal 简明教程(一、旋转的BOX)
  • 原文地址:https://www.cnblogs.com/icbcfang/p/4769872.html
Copyright © 2020-2023  润新知