zoukankan      html  css  js  c++  java
  • 并发编程(三)—— ReentrantLock的用法

      ReentrantLock是Java并发包中提供的一个可重入的互斥锁ReentrantLocksynchronized在基本用法,行为语义上都是类似的,同样都具有可重入性。只不过相比原生的Synchronized,ReentrantLock增加了一些高级的扩展功能,比如它可以实现公平锁,同时也可以绑定多个Conditon

    可重入性/公平锁/非公平锁

    可重入性

          所谓的可重入性,就是可以支持一个线程对锁的重复获取,原生的synchronized就具有可重入性,一个用synchronized修饰的递归方法,当线程在执行期间,它是可以反复获取到锁的,而不会出现自己把自己锁死的情况。ReentrantLock也是如此,在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。

    公平锁/非公平锁

      所谓公平锁,顾名思义,意指锁的获取策略相对公平,当多个线程在获取同一个锁时,必须按照锁的申请时间来依次获得锁,排排队,不能插队;非公平锁则不同,当锁被释放时,等待中的线程均有机会获得锁。synchronized是非公平锁,ReentrantLock默认也是非公平的,但是可以通过带boolean参数的构造方法指定使用公平锁,但非公平锁的性能一般要优于公平锁。

      synchronized是Java原生的互斥同步锁,使用方便,对于synchronized修饰的方法或同步块,无需再显式释放锁。而ReentrantLock做为API层面的互斥锁,需要显式地去加锁解锁。采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。

    class X {
        private final ReentrantLock lock = new ReentrantLock();
        // ...
     
        public void m() {
          lock.lock();  // 加锁
          try {
            // ... 函数主题
          } finally {
            lock.unlock() //解锁
          }
        }
    }

    源码分析

      接下来我们从源码角度来看看ReentrantLock的实现原理,它是如何保证可重入性,又是如何实现公平锁的。

    1、无参构造器(默认为非公平锁)

    public ReentrantLock() {
         sync = new NonfairSync();//默认是非公平的
    }

    sync是ReentrantLock内部实现的一个同步组件,它是Reentrantlock的一个静态内部类,继承于AQS。

    2、带布尔值的构造器(是否公平)

    public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();//fair为true,公平锁;反之,非公平锁
    }

    此处可以指定是否采用公平锁,FailSync和NonFailSync亦为Reentrantlock的静态内部类,都继承于Sync

    3、lock()

    public void lock() {
            sync.lock();//代理到Sync的lock方法上
    }

    Sync的lock方法是抽象的,实际的lock会代理到FairSync或是NonFairSync上(根据用户的选择来决定,公平锁还是非公平锁)

    4、unlock()

    public void unlock() {
            sync.release(1);//释放锁
    }

    释放锁,调用sync的release方法。

    5、tryLock()

    Lock lock = ...;
    if(lock.tryLock()) {
         try{
             //处理任务
         }catch(Exception ex){
             
         }finally{
             lock.unlock();   //释放锁
         } 
    }else {
        //如果不能获取锁,则直接做其他事情
    }

    tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false。

    6、newCondition()

    public Condition newCondition() {
            return sync.newCondition();
    }

    获取一个conditon,ReentrantLock支持多个Condition

    7、await()

    public class MyService {
    
        private Lock lock = new ReentrantLock();
        private Condition condition=lock.newCondition();
        public void testMethod() {
            
            try {
                lock.lock();
                System.out.println("开始wait");
                condition.await();
                for (int i = 0; i < 5; i++) {
                    System.out.println("ThreadName=" + Thread.currentThread().getName()
                            + (" " + (i + 1)));
                }
            } catch (InterruptedException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }
            finally
            {
                lock.unlock();
            }
        }
    
    }

    通过创建Condition对象来使线程wait,必须先执行lock.lock方法获得锁

    8、signal()

    public void signal() {
            try {
                lock.lock();
                condition.signal();
            } finally {
                lock.unlock();
            }
    }

    condition对象的signal方法可以唤醒wait线程

    9、创建多个condition对象

      一个condition对象的signal(signalAll)方法和该对象的await方法是一一对应的,也就是一个condition对象的signal(signalAll)方法不能唤醒其他condition对象的await方法

    ABC循环打印20遍

      1 package main.java.Juc;
      2 
      3 import java.util.concurrent.locks.Condition;
      4 import java.util.concurrent.locks.Lock;
      5 import java.util.concurrent.locks.ReentrantLock;
      6 
      7 /*
      8  * 编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。
      9  *    如:ABCABCABC…… 依次递归
     10  */
     11 public class TestABCAlternate {
     12     
     13     public static void main(String[] args) {
     14         AlternateDemo ad = new AlternateDemo();
     15         
     16         new Thread(new Runnable() {
     17             @Override
     18             public void run() {
     19                 for (int i = 1; i <= 20; i++) {
     20                     ad.loopA(i);
     21                 }
     22             }
     23         }, "A").start();
     24         
     25         new Thread(new Runnable() {
     26             @Override
     27             public void run() {
     28                 for (int i = 1; i <= 20; i++) {
     29                     ad.loopB(i);
     30                 }
     31             }
     32         }, "B").start();
     33         
     34         new Thread(new Runnable() {
     35             @Override
     36             public void run() {
     37                 for (int i = 1; i <= 20; i++) {
     38                     ad.loopC(i);
     39                     System.out.println("-----------------------------------");
     40                 }
     41             }
     42         }, "C").start();
     43     }
     44 
     45 }
     46 
     47 class AlternateDemo{
     48     
     49     private int number = 1; //当前正在执行线程的标记
     50     
     51     private Lock lock = new ReentrantLock();
     52     private Condition condition1 = lock.newCondition();
     53     private Condition condition2 = lock.newCondition();
     54     private Condition condition3 = lock.newCondition();
     55     
     56     /**
     57      * @param totalLoop : 循环第几轮
     58      */
     59     public void loopA(int totalLoop){
     60         lock.lock();
     61         try {
     62             //1. 判断
     63             if(number != 1){
     64                 condition1.await();
     65             }
     66             //2. 打印
     67             for (int i = 1; i <= 1; i++) {
     68                 System.out.println(Thread.currentThread().getName() + "	" + i + "	" + totalLoop);
     69             }
     70             //3. 唤醒
     71             number = 2;
     72             condition2.signal();
     73         } catch (Exception e) {
     74             e.printStackTrace();
     75         } finally {
     76             lock.unlock();
     77         }
     78     }
     79     
     80     public void loopB(int totalLoop){
     81         lock.lock();
     82         try {
     83             //1. 判断
     84             if(number != 2){
     85                 condition2.await();
     86             }
     87             //2. 打印
     88             for (int i = 1; i <= 1; i++) {
     89                 System.out.println(Thread.currentThread().getName() + "	" + i + "	" + totalLoop);
     90             }
     91             //3. 唤醒
     92             number = 3;
     93             condition3.signal();
     94         } catch (Exception e) {
     95             e.printStackTrace();
     96         } finally {
     97             lock.unlock();
     98         }
     99     }
    100     
    101     public void loopC(int totalLoop){
    102         lock.lock();
    103         try {
    104             //1. 判断
    105             if(number != 3){
    106                 condition3.await();
    107             }
    108             //2. 打印
    109             for (int i = 1; i <= 1; i++) {
    110                 System.out.println(Thread.currentThread().getName() + "	" + i + "	" + totalLoop);
    111             }
    112             //3. 唤醒
    113             number = 1;
    114             condition1.signal();
    115         } catch (Exception e) {
    116             e.printStackTrace();
    117         } finally {
    118             lock.unlock();
    119         }
    120     }
    121     
    122 }

    运行结果:

    代码分析:

      三个线程分别循环20次调用loopA、loopB、loopC打印,但是不确定是哪个方法先被调用到,如果是loopB先调用,则loopB方法先获取到锁,loopA和loopC等待锁,此时线程执行标记number=1,代码84行处为true,则condition2.await();如果需要唤醒此线程,则需要用condition2来唤醒,此时线程交出锁;

      如果loopA获取了锁,loopB和loopC等待锁,此时线程执行标记number=1,代码63行处为false,则执行67行打印,打印完则用condition2.signal()唤醒打印loopB的线程,接着loopB的线程去打印B,线程loopB打印完毕去唤醒打印loopC的线程,打印完loopC再唤醒loopA,如此循环20次。

     总结

     1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

     2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

     3、Lock类可以创建Condition对象,Condition对象用来是线程等待和唤醒线程,需要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,所以也就可以实现唤醒指定类的线程

  • 相关阅读:
    基于模糊Choquet积分的目标检测算法
    Android开发5:布局管理器2(表格布局TableLayout)
    JAVA WEB开发环境搭建教程
    linux下自助获取帮助
    dsp下基于双循环缓冲队列的视频采集和显示记录
    找工作笔试面试那些事儿(11)---数据库知识总结(2)范式
    【Todo】Zookeeper系列文章
    VC2010对Excel的操作
    hdu2647解题报告
    premake 在64位Ubuntu系统下编译32位GCC程序
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/10037209.html
Copyright © 2011-2022 走看看