java中的可重入锁ReentrantLock

翻译自geeksforgeeks。

背景
在Java中实现线程同步的传统方法是使用synchronized关键字。
虽然它提供了基本同步功能,但synchronized的使用比较死板。
比如说,一个线程只能锁一次。同步块不提供任何等待队列的机制,并且在一个线程退出后,任何线程都可以获取锁。这可能导致很长一段时间内某些其他线程的资源匮乏。
Java中提供了可重入锁(ReentrantLock),以提供更大灵活性的同步。

什么是可重入锁?
ReentrantLock类实现Lock接口,并在方法访问共享资源时为其提供同步。操作共享资源的代码会被锁定和解锁方法的调用所包围。这会锁定当前工作线程并阻止尝试锁定共享资源的所有其他线程。
顾名思义,ReentrantLock允许线程不止一次地锁定资源。当线程首次进入锁定状态时,保持计数(hold count)设置为1。在解锁之前,线程可以再次重新进入锁定状态,并且每次保持计数增加1。对于每个解锁请求,保持计数减1,当保持计数为0时,资源解锁。可重入锁还提供公平参数,通过该公平参数锁将遵守请求的顺序,也即在线程解锁资源之后,锁将会被已经等待最长时间的线程获得。这种公平模式是通过将true传递给锁的构造函数来设置的。

这些锁以下列方式使用:

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

始终在finally块中调用unlock语句,以确保即使在方法体中抛出异常(try块)也会释放锁定。

ReentrantLock的方法:

  • lock():调用lock()方法将保持计数(hold count)增加1,如果共享资源最初是空闲的,则给予线程锁定。
  • unlock():调用unlock()方法将保持计数减1.当此计数达到零时,资源被释放。
  • tryLock():如果资源未被任何其他线程持有,则调用tryLock()将返回true,并且保持计数将增加1。如果资源不是空闲的,则该方法返回false并且线程未被阻塞而是退出。
  • tryLock(long timeout,TimeUnit unit):对于每个方法,线程等待参数定义的特定时间段,以在退出之前获取资源的锁。
  • lockInterruptibly():如果资源是空闲的,则此方法获取锁定,同时允许线程在获取资源时被某个其他线程中断。 这意味着如果当前线程正在等待锁定但其他线程请求锁定,则当前线程将被中断并立即返回而不获取锁定。
  • getHoldCount():此方法返回资源的锁的保持计数。
  • isHeldByCurrentThread():如果资源上的锁由当前线程保持,则此方法返回true。

ReentrantLock 例子
在下面的教程中,我们将看一下Reentrant Locks的基本示例。
要遵循的步骤

  1. 创建ReentrantLock的对象
  2. 创建一个worker(Runnable Object)来执行并将锁传递给该对象
  3. 使用lock()方法获取共享资源上的锁
  4. 完成工作后,调用unlock()方法释放锁定
// 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(); 
} 
} 

输出:

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. 用于构造锁对象的公平参数会降低程序的吞吐量。

ReentrantLocksynchronization的更好替代品,它提供了许多synchronization没有提供的功能。然而,这些明显的好处的存在并不足以每次都使用ReentrantLock而非synchronized,要根据是否需要ReentrantLock提供的灵活性来做决定。

你可能感兴趣的:(Java)