zoukankan      html  css  js  c++  java
  • java线程的同步控制--重入锁ReentrantLock

     我们常用的synchronized关键字是一种最简单的线程同步控制方法,它决定了一个线程是否可以访问临界区资源。同时Object.wait() 和Object.notify()方法起到了线程等待和通知的作用。这些工具对于实现复杂的多线程协作起到了重要的作用。
        这里,我们介绍一种synchronized,Object.wait() 和Object.notify()方法的替代品--重入锁。首先看下重入锁的例子:
     
        public ReentrantLock lock = new ReentrantLock();//声明一把重入锁
         ......
         lock.lock();//开始加锁
         doSomething();//需要同步的代码块
         lock.unlock();//释放锁
     
         从上述代码可以看出,与synchronized相比,重入锁有着显式的操作过程,开发人员需要手动加锁,释放锁。因此,重入锁对逻辑控制的灵活性要远远高于synchronized,但是,在退出临界区时,必须记得释放锁,否则其它线程再也无法访问临界区资源了。
     
         之所以叫重入锁,是因为这种锁是可以反复进入的(当然仅仅局限于同一个线程),例如下面的形式也是可以的:
     
         lock.lock();//开始加锁
         lock.lock();
         doSomething();//需要同步的代码块
         lock.unlock();//释放锁
         lock.unlock();
     
        一个线程连续多次获得同一把锁,是允许的,否则的话,同一个线程在第二次获得锁时,会和自己产生死锁。但是需要注意的是,如果同一个线程多次获得锁,释放锁的时候必须释放相同次数的锁。如果释放少了,其它线程无法进入临界区,如果释放多了,则会抛出IllegalMonitorStateException异常。
     
        除了使用上的灵活性外,重入锁还提供了一些高级功能,如中断响应、限时等待、公平锁等功能。
     
        中断响应:
        对于synchronized,如果一个线程在等待锁,结果要么它获得这把锁执行其任务,要么就一直等待锁。而使用重入锁,就有了另一种选择,那就是线程可以被中断,即线程在等待锁的过程中,程序可以根据需求取消对锁的请求。中断正是提供了这种机制,使程序在等待锁时,依然能够收到通知,被告知无需继续等待,这种情况对于处理死锁是有很大帮助的。
        下面模拟一段死锁,并通过中断解决这个死锁:
     
         //声明两把重入锁
        public ReentrantLock lock1 = new ReentrantLock();
        public ReentrantLock lock2 = new ReentrantLock();
     
        class MyThread implements Runnable(){
              private int lock;
              public MyThread (int lock){
                  this.lock = lock;
              }
              @Override
              public void run(){
                  if(lock == 1){
                       lock1.lockInterruptibly();//申请锁1,如果要使用中断功能,需使用lockInterruptibly()申请锁
                       Thread.sleep(5000);
                        lock2.lockInterruptibly();//申请锁2
                  }else{
                        lock2.lockInterruptibly();//申请锁2
                       Thread.sleep(5000);
                        lock1.lockInterruptibly();//申请锁1
                  }
                  
                  lock1.unlock();//释放锁
                  lock2.unlock();
              }
         }
     
         public static void main(String[] s){
              //t1先请求锁1,再请求锁2
              //t2先请求锁2,再请求锁1
              //这样两个线程就会互相等待对方,造成死锁
              Thread t1 = new Thread(new MyThread(1));
              Thread t2 = new Thread(new MyThread(2));
              t1.start();
              t2.start();
              //上面两个线程已经形成死锁
              Thread.sleep(1000);
              t2.interrupt();//中断T2,放弃对锁的请求,并释放持有的锁
        }
     
        限时等待:
        除了等待外部通知外,要避免死锁还有一种方法,那就是限时等待。给定一个等待时间,如果在这个超出这个时间,线程还没获得锁,则放弃等待。我们可以使用tryLock()方法进行一次限时等待,如lock.tryLock(5,TimeUnit.SECONDS)表示设置5秒的等待时长,如果超过5s还未得到锁,则返回false。
     
        if(lock.tryLock(5,TimeUnit.SECONDS)){
              doSomething();
        }else{
              System.out.println("没有获得锁,你滚吧。");
        }
     
        tryLock()方法也可以不带参数运行,这种情况下,线程会请求锁,如果请求不到,立即返回false,无需等待,也不会产生死锁。
     
        公平锁:
         大多数情况下,锁的申请是非公平的,即先等待的线程不一定先获得锁,对排队等待锁的线程来说是不公平的。而公平的锁,就会按照先来后到的顺序分配锁。公平锁的一大特点就是,它不会产生饥饿现象。只要你排队,最终还是可以等到资源的。
        如果我们使用synchronized关键字,那么产生的锁就是非公平的,而重入锁允许我们对其公平性进行设置,它提供了一个构造函数:
        public ReentrantLock(boolean fair)
        当fair为true时,表示锁是公平的。虽然公平锁很优美,但是要实现公平锁必然要维护一个有序队列,因此公平锁的实现成本较高,性能相对也比较低下,因此,默认情况下,锁是非公平的。
     
        总结:
        ReentrantLock有几个重要的方法:
        1. lock():获得锁,如果锁被占用,则等待
        2. lockInterruptibly():获得锁,但优先响应中断
        3. tryLock():尝试获得锁,并制定等待时间,超时返回false
        4. unlock():释放锁
     
     
        参考资料:
        《java高并发程序设计》
     

    欢迎关注我的微信公众号(Sunnick,请扫码关注),随时留言交流~

  • 相关阅读:
    运营设计方法论
    使用 typescript ,提升 vue 项目的开发体验(2)
    PAT 1078. 字符串压缩与解压
    PAT 1077. 互评成绩计算
    PAT 1076. Wifi密码
    PAT 1075. 链表元素分类
    PAT 1074. 宇宙无敌加法器
    PAT 1073. 多选题常见计分法
    PAT 1072. 开学寄语
    PAT 1071. 小赌怡情
  • 原文地址:https://www.cnblogs.com/sunnick/p/8982361.html
Copyright © 2011-2022 走看看