翻译自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的基本示例。
要遵循的步骤
- 创建
ReentrantLock
的对象- 创建一个
worker(Runnable Object)
来执行并将锁传递给该对象- 使用
lock()
方法获取共享资源上的锁- 完成工作后,调用
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
要点:
- 可能会忘记在finally块中调用
unlock()
方法导致程序中的错误。确保在线程退出之前释放锁定。 - 用于构造锁对象的公平参数会降低程序的吞吐量。
ReentrantLock
是synchronization
的更好替代品,它提供了许多synchronization
没有提供的功能。然而,这些明显的好处的存在并不足以每次都使用ReentrantLock
而非synchronized
,要根据是否需要ReentrantLock
提供的灵活性来做决定。