📜  Java中的可重入锁

📅  最后修改于: 2022-05-13 01:54:36.917000             🧑  作者: Mango

Java中的可重入锁

背景

在Java中实现线程同步的传统方式是使用 synchronized 关键字。虽然它提供了一定的基本同步,但 synchronized 关键字在使用上相当严格。例如,一个线程只能获取一次锁。同步块不提供任何等待队列的机制,在一个线程退出后,任何线程都可以获取锁。这可能会导致其他线程在很长一段时间内资源匮乏。
Java中提供了可重入锁以提供具有更大灵活性的同步。

什么是可重入锁?

ReentrantLock 类实现了 Lock 接口,并在访问共享资源时为方法提供同步。操作共享资源的代码被锁定和解锁方法的调用包围。这为当前工作线程提供了一个锁定,并阻止了所有其他试图锁定共享资源的线程。

顾名思义,ReentrantLock 允许线程多次进入资源锁。当线程第一次进入锁时,保持计数设置为 1。在解锁之前,线程可以重新进入锁,每次保持计数加一。对于每个解锁请求,保持计数减 1,当保持计数为 0 时,资源被解锁。

可重入锁还提供了一个公平参数,通过该参数,锁将遵循锁请求的顺序,即在线程解锁资源后,锁将转到等待时间最长的线程。这种公平模式是通过将 true 传递给锁的构造函数来设置的。
这些锁的使用方式如下:

Java
public void some_method()
{
        reentrantlock.lock();
        try
        {
            //Do some work
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            reentrantlock.unlock();
        }
         
}


Java
// Java code to illustrate Reentrant Locks
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
 
class worker implements Runnable
{
  String name;
  ReentrantLock re;
  public worker(ReentrantLock rl, String n)
  {
    re = rl;
    name = n;
  }
  public void run()
  {
    boolean done = false;
    while (!done)
    {
      //Getting Outer Lock
      boolean ans = re.tryLock();
 
      // Returns True if lock is free
      if(ans)
      {
        try
        {
          Date d = new Date();
          SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
          System.out.println("task name - "+ name
                     + " outer lock acquired at "
                     + ft.format(d)
                     + " Doing outer work");
          Thread.sleep(1500);
 
          // Getting Inner Lock
          re.lock();
          try
          {
            d = new Date();
            ft = new SimpleDateFormat("hh:mm:ss");
            System.out.println("task name - "+ name
                       + " inner lock acquired at "
                       + ft.format(d)
                       + " Doing inner work");
            System.out.println("Lock Hold Count - "+ re.getHoldCount());
            Thread.sleep(1500);
          }
          catch(InterruptedException e)
          {
            e.printStackTrace();
          }
          finally
          {
            //Inner lock release
            System.out.println("task name - " + name +
                       " releasing inner lock");
 
            re.unlock();
          }
          System.out.println("Lock Hold Count - " + re.getHoldCount());
          System.out.println("task name - " + name + " work done");
 
          done = true;
        }
        catch(InterruptedException e)
        {
          e.printStackTrace();
        }
        finally
        {
          //Outer lock release
          System.out.println("task name - " + name +
                     " releasing outer lock");
 
          re.unlock();
          System.out.println("Lock Hold Count - " +
                       re.getHoldCount());
        }
      }
      else
      {
        System.out.println("task name - " + name +
                      " waiting for lock");
        try
        {
          Thread.sleep(1000);
        }
        catch(InterruptedException e)
        {
          e.printStackTrace();
        }
      }
    }
  }
}
 
public class test
{
  static final int MAX_T = 2;
  public static void main(String[] args)
  {
    ReentrantLock rel = new ReentrantLock();
    ExecutorService pool = Executors.newFixedThreadPool(MAX_T);
    Runnable w1 = new worker(rel, "Job1");
    Runnable w2 = new worker(rel, "Job2");
    Runnable w3 = new worker(rel, "Job3");
    Runnable w4 = new worker(rel, "Job4");
    pool.execute(w1);
    pool.execute(w2);
    pool.execute(w3);
    pool.execute(w4);
    pool.shutdown();
  }
}


总是在 finally 块中调用解锁语句,以确保即使在方法体(try 块)中抛出异常也能释放锁。

ReentrantLock() 方法

  • lock():调用 lock() 方法将保持计数加 1,如果共享资源最初是空闲的,则将锁分配给线程。
  • unlock():调用unlock()方法将持有计数减1。当这个计数达到零时,资源被释放。
  • tryLock():如果资源未被任何其他线程持有,则调用 tryLock() 返回 true,并且持有计数加 1。如果资源不是空闲的,则该方法返回 false,线程不会被阻塞,而是退出。
  • tryLock(long timeout, TimeUnit unit):根据方法,线程在退出前等待方法参数定义的一定时间段来获取资源的锁。
  • lockInterruptibly():如果资源空闲,则此方法获取锁,同时允许线程在获取资源时被其他线程中断。意思是如果当前线程正在等待锁,但是其他线程请求锁,那么当前线程将被中断并立即返回,而不获取锁。
  • getHoldCount():此方法返回资源上持有的锁的数量。
  • isHeldByCurrentThread():如果当前线程持有资源的锁,则此方法返回 true。

ReentrantLock() 示例

在下面的教程中,我们将看一个可重入锁的基本示例。

应遵循的步骤

1. Create an object of ReentrantLock
2. Create a worker(Runnable Object) to execute and pass the lock to the object
3. Use the lock() method to acquire the lock on shared resource
4. After completing work, call unlock() method to release the lock 

下面是问题陈述的实现:

Java

// Java code to illustrate Reentrant Locks
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
 
class worker implements Runnable
{
  String name;
  ReentrantLock re;
  public worker(ReentrantLock rl, String n)
  {
    re = rl;
    name = n;
  }
  public void run()
  {
    boolean done = false;
    while (!done)
    {
      //Getting Outer Lock
      boolean ans = re.tryLock();
 
      // Returns True if lock is free
      if(ans)
      {
        try
        {
          Date d = new Date();
          SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
          System.out.println("task name - "+ name
                     + " outer lock acquired at "
                     + ft.format(d)
                     + " Doing outer work");
          Thread.sleep(1500);
 
          // Getting Inner Lock
          re.lock();
          try
          {
            d = new Date();
            ft = new SimpleDateFormat("hh:mm:ss");
            System.out.println("task name - "+ name
                       + " inner lock acquired at "
                       + ft.format(d)
                       + " Doing inner work");
            System.out.println("Lock Hold Count - "+ re.getHoldCount());
            Thread.sleep(1500);
          }
          catch(InterruptedException e)
          {
            e.printStackTrace();
          }
          finally
          {
            //Inner lock release
            System.out.println("task name - " + name +
                       " releasing inner lock");
 
            re.unlock();
          }
          System.out.println("Lock Hold Count - " + re.getHoldCount());
          System.out.println("task name - " + name + " work done");
 
          done = true;
        }
        catch(InterruptedException e)
        {
          e.printStackTrace();
        }
        finally
        {
          //Outer lock release
          System.out.println("task name - " + name +
                     " releasing outer lock");
 
          re.unlock();
          System.out.println("Lock Hold Count - " +
                       re.getHoldCount());
        }
      }
      else
      {
        System.out.println("task name - " + name +
                      " waiting for lock");
        try
        {
          Thread.sleep(1000);
        }
        catch(InterruptedException e)
        {
          e.printStackTrace();
        }
      }
    }
  }
}
 
public class test
{
  static final int MAX_T = 2;
  public static void main(String[] args)
  {
    ReentrantLock rel = new ReentrantLock();
    ExecutorService pool = Executors.newFixedThreadPool(MAX_T);
    Runnable w1 = new worker(rel, "Job1");
    Runnable w2 = new worker(rel, "Job2");
    Runnable w3 = new worker(rel, "Job3");
    Runnable w4 = new worker(rel, "Job4");
    pool.execute(w1);
    pool.execute(w2);
    pool.execute(w3);
    pool.execute(w4);
    pool.shutdown();
  }
}

样品执行

Output:
task name - Job2 waiting for lock
task name - Job1 outer lock acquired at 09:49:42 Doing outer work
task name - Job2 waiting for lock
task name - Job1 inner lock acquired at 09:49:44 Doing inner work
Lock Hold Count - 2
task name - Job2 waiting for lock
task name - Job2 waiting for lock
task name - Job1 releasing inner lock
Lock Hold Count - 1
task name - Job1 work done
task name - Job1 releasing outer lock
Lock Hold Count - 0
task name - Job3 outer lock acquired at 09:49:45 Doing outer work
task name - Job2 waiting for lock
task name - Job3 inner lock acquired at 09:49:47 Doing inner work
Lock Hold Count - 2
task name - Job2 waiting for lock
task name - Job2 waiting for lock
task name - Job3 releasing inner lock
Lock Hold Count - 1
task name - Job3 work done
task name - Job3 releasing outer lock
Lock Hold Count - 0
task name - Job4 outer lock acquired at 09:49:48 Doing outer work
task name - Job2 waiting for lock
task name - Job4 inner lock acquired at 09:49:50 Doing inner work
Lock Hold Count - 2
task name - Job2 waiting for lock
task name - Job2 waiting for lock
task name - Job4 releasing inner lock
Lock Hold Count - 1
task name - Job4 work done
task name - Job4 releasing outer lock
Lock Hold Count - 0
task name - Job2 outer lock acquired at 09:49:52 Doing outer work
task name - Job2 inner lock acquired at 09:49:53 Doing inner work
Lock Hold Count - 2
task name - Job2 releasing inner lock
Lock Hold Count - 1
task name - Job2 work done
task name - Job2 releasing outer lock
Lock Hold Count - 0

要点

  1. 人们可能会忘记在 finally 块中调用 unlock() 方法,从而导致程序出现错误。确保在线程退出之前释放锁。
  2. 用于构造锁对象的公平参数会降低程序的吞吐量。

ReentrantLock 是同步的更好替代品,它提供了许多 synchronized 没有提供的功能。然而,这些明显好处的存在并不足以成为总是喜欢 ReentrantLock 进行同步的充分理由。相反,根据您是否需要 ReentrantLock 提供的灵活性来做出决定。