简介

  • ReentrantLock,可重入锁,是一种递归无阻塞的同步机制。它可以等同于 synchronized 的使用,但是 ReentrantLock 提供了比 synchronized 更强大、灵活的锁机制,可以减少死锁发生的概率。
    • 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。

示例

  • ReentrantLock,意思是“可重入锁”,ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。下面通过一些实例看具体看一下如何使用ReentrantLock。

  • lock()的错误使用方法

    • 代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      public class Test {
      private ArrayList<Integer> arrayList = new ArrayList<Integer>();
      public static void main(String[] args) {
      final Test test = new Test();

      new Thread(){
      public void run() {
      test.insert(Thread.currentThread());
      };
      }.start();

      new Thread(){
      public void run() {
      test.insert(Thread.currentThread());
      };
      }.start();
      }

      public void insert(Thread thread) {
      Lock lock = new ReentrantLock(); //注意这个地方
      lock.lock();
      try {
      System.out.println(thread.getName()+"得到了锁");
      for(int i=0;i<5;i++) {
      arrayList.add(i);
      }
      } catch (Exception e) {
      // TODO: handle exception
      }finally {
      System.out.println(thread.getName()+"释放了锁");
      lock.unlock();
      }
      }
      }
    • 各位朋友先想一下这段代码的输出结果是什么?

      1
      2
      3
      4
      Thread-0得到了锁
      Thread-1得到了锁
      Thread-0释放了锁
      Thread-1释放了锁
    • 也许有朋友会问,怎么会输出这个结果?第二个线程怎么会在第一个线程释放锁之前得到了锁?原因在于,在insert方法中的lock变量是局部变量,每个线程执行该方法时都会保存一个副本,那么理所当然每个线程执行到lock.lock()处获取的是不同的锁,所以就不会发生冲突。

    • 知道了原因改起来就比较容易了,只需要将lock声明为类的属性即可。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      public class Test {
      private ArrayList<Integer> arrayList = new ArrayList<Integer>();
      private Lock lock = new ReentrantLock(); //注意这个地方
      public static void main(String[] args) {
      final Test test = new Test();

      new Thread(){
      public void run() {
      test.insert(Thread.currentThread());
      };
      }.start();

      new Thread(){
      public void run() {
      test.insert(Thread.currentThread());
      };
      }.start();
      }

      public void insert(Thread thread) {
      lock.lock();
      try {
      System.out.println(thread.getName()+"得到了锁");
      for(int i=0;i<5;i++) {
      arrayList.add(i);
      }
      } catch (Exception e) {
      // TODO: handle exception
      }finally {
      System.out.println(thread.getName()+"释放了锁");
      lock.unlock();
      }
      }
      }
    • 这样就是正确地使用Lock的方法了。

  • tryLock()的使用方法

    • 代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      public class Test {
      private ArrayList<Integer> arrayList = new ArrayList<Integer>();
      private Lock lock = new ReentrantLock(); //注意这个地方
      public static void main(String[] args) {
      final Test test = new Test();

      new Thread(){
      public void run() {
      test.insert(Thread.currentThread());
      };
      }.start();

      new Thread(){
      public void run() {
      test.insert(Thread.currentThread());
      };
      }.start();
      }

      public void insert(Thread thread) {
      if(lock.tryLock()) {
      try {
      System.out.println(thread.getName()+"得到了锁");
      for(int i=0;i<5;i++) {
      arrayList.add(i);
      }
      } catch (Exception e) {
      // TODO: handle exception
      }finally {
      System.out.println(thread.getName()+"释放了锁");
      lock.unlock();
      }
      } else {
      System.out.println(thread.getName()+"获取锁失败");
      }
      }
      }
    • 输出结果:

      1
      2
      3
      Thread-0得到了锁
      Thread-1获取锁失败
      Thread-0释放了锁
  • lockInterruptibly()响应中断的使用方法:

    • 代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      public class Test {
      private Lock lock = new ReentrantLock();
      public static void main(String[] args) {
      Test test = new Test();
      MyThread thread1 = new MyThread(test);
      MyThread thread2 = new MyThread(test);
      thread1.start();
      thread2.start();

      try {
      Thread.sleep(2000);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      thread2.interrupt();
      }

      public void insert(Thread thread) throws InterruptedException{
      lock.lockInterruptibly(); //注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出
      try {
      System.out.println(thread.getName()+"得到了锁");
      long startTime = System.currentTimeMillis();
      for( ; ;) {
      if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
      break;
      //插入数据
      }
      }
      finally {
      System.out.println(Thread.currentThread().getName()+"执行finally");
      lock.unlock();
      System.out.println(thread.getName()+"释放了锁");
      }
      }
      }

      class MyThread extends Thread {
      private Test test = null;
      public MyThread(Test test) {
      this.test = test;
      }
      @Override
      public void run() {

      try {
      test.insert(Thread.currentThread());
      } catch (InterruptedException e) {
      System.out.println(Thread.currentThread().getName()+"被中断");
      }
      }
      }
    • 运行之后,发现thread2能够被正确中断

      1
      2
      Thread-0得到了锁
      Thread-1被中断

总结

  • 与 synchronized 相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。
  • ReentrantLock 还提供了条件 Condition ,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock 更加适合(以后会阐述Condition)。
  • ReentrantLock 提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而 synchronized 则一旦进入锁请求要么成功要么阻塞,所以相比 synchronized 而言,ReentrantLock会不容易产生死锁些。
  • ReentrantLock 支持更加灵活的同步代码块,但是使用 synchronized 时,只能在同一个 synchronized 块结构中获取和释放。注意,ReentrantLock 的锁释放一定要在 finally 中处理,否则可能会产生严重的后果。
  • ReentrantLock 支持中断处理,且性能较 synchronized 会好些。

参考转载