老骥伏枥,志在千里;烈士暮年,壮心不已。———曹操《龟虽寿》
意思是老的千里马虽然伏在马槽旁,雄心壮志仍是驰骋千里;壮志凌云的人士即便到了晚年,奋发思进的心也永不止息。
Lock 和 ReentrantLock
与内部加锁不同,Lock 提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显示的。Lock的实现必须提供具有与内部加锁相同的内存可见性的语义。但是加锁的语义,调度算法,顺序保证,性能特性这些可以不同。
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock 实现了 Lock 接口,提供了与 sysnchronized 相同的互斥和内存可见性的保证。ReentrantLock 支持 Lock 接口定义的所有获取锁的模式,与 sysnchronized 相比, ReentrantLock 为处理不可用的锁提供了更多灵活性。
为什么要创建与内部锁如此相似的机制呢?因为内部锁有一些功能上的局限,不能中断那些正在等待获取锁的线程,并且在请求锁失败的情况下,必须无限等待。
使用 ReentrantLock 的范例如下:
Lock lock = new ReentrantLock();
...
lock.lock();
try {
//更新对象的状态
} finally {
lock.unlock();
}
记得一定要记住使用 finally 释放 Lock。
可轮询的和可定时的锁请求
由 tryLock 实现可定时和可轮询的获锁模式,它具有更完善的错误恢复机制,可以规避死锁的发生。
下面举例来获取两个锁,如果不能同时获取两个锁,就回退并重新尝试。如果一定时间内,没有能获得所有需要的锁,transferMoney返回一个失败状态,这样操作就能优雅的失败了。
//使用 tryLock 避免顺序死锁的发生
public boolean transferMoney(Account fromAcc, Account toAcc, DollarAmount amount, long timeout, TimeUnit unit) throws InsufficientFundsException, InterruptedException {
long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
long randMod = getRandomDelayModuleNanos(timeout, unit);
long stopTime = System.nanoTime() + unit.toNanos(timeout);
while(true) {
if(fromAcc.lock.tryLock()) {
try{
if(toAcc.lock.tryLock()) {
try{
if(fromAcc.getBalance().compareTo(amount) < 0){
throws new InsufficientFundsException();
}
else{
fromAcc.debit(amount);
toAcc.credit(amount);
return true;
}
}finally{
toAcc.lock.unlock();
}
}
}finally{
fromAcc.lock.unlock();
}
}
if(System.nanoTime() < stopTime)
return false;
NANOSECONDS.sleep(fixedDelay + randMod.nextLong()%randMod);
}
}
对于实现那些具有时间限制的活动,定时锁同样非常有用。定时锁能在时间预算内设定相应的超时,超时后能够提前返回。而内部所一旦开始请求,锁就不能停止了。
//实现可定时的锁
public boolean trySendOnSharedLine(String message, long timeout, TimeUnit unit) throws InterruptedException {
if(!lock.tryLock(nanosToLock, NANOSECONDS)) {
return false;
}
try{
return sendOnSharedLine(message);
} finally {
lock.unlock();
}
}
可中断的锁获取操作
一个可中断的锁获取操作的规范形式,要比一个普通的锁稍微复杂一些,因为需要两个 try 块。如果可中断的锁获取操作抛出了 InterruptedException, 那么标准的 try-finally 加锁的模式就非常有效了。下面代码中,sendOnSharedLine,我们就能在可取消的任务中调用它了。定时的 tryLock同样响应中断,因此当你在需要获取可定时可中断的锁时,可以使用这个方法。
public boolean sendOnSharedLine(String messages) throws InterruptedException{
lock.lockInterruptibly();
try{
return cancellableSendOnSharedLine(messages);
} finally{
lock.unlock();
}
}
private boolean cancellableSendOnSharedLine(String messages) throws InterruptedException{...}
在synchronized 和 ReentrantLock 之间进行选择
在内部锁不能够满足使用时,ReentrantLock 才被作为更高级的工具。当你需要以下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。否则,请使用 synchronized。
举例,实现上面的可轮询,可中断,可定时的显示锁。代码清单如下:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest{
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(new Task1(), "Thrad-1").start();
new Thread(new Task2(), "Thrad-2").start();
new Thread(new Task3(), "Thrad-3").start();
Thread t4 = new Thread(new Task4(), "Thrad-4");
t4.start();
t4.interrupt();
}
//任务一,直接获取显示锁,并执行500ms
public static class Task1 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " get lock success!");
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " release lock success!");
}
}
}
//任务二,轮询获取锁,直至获取成功为止,获取成功后执行500ms
public static class Task2 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
while(!lock.tryLock()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " get lock failed");
}
System.out.println(Thread.currentThread().getName() + " get lock success!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " release lock success!");
}
}
}
//可定时获取,超时后获取失败,执行1s
public static class Task3 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
try {
//1s内获取得到锁则成功,否则失败
if(lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
try {
System.out.println(Thread.currentThread().getName() + " get lock successed");
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " release lock success!");
}
} else {
System.out.println(Thread.currentThread().getName() + " get lock failed");
}
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
//获取可中断锁,中断则直接退出
public static class Task4 implements Runnable {
@Override
public void run() {
try {
lock.lockInterruptibly();
try {
if(!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "isInterrpted now");
}
System.out.println(Thread.currentThread().getName() + " get lock successed");
Thread.sleep(5000);
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " release lock success!");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted");
}
}
}
}
执行结果如下:


本文详细介绍了Lock接口与ReentrantLock的具体实现及其优势,包括可轮询的、可定时的与可中断的锁获取操作等高级特性。通过具体代码示例展示了如何使用ReentrantLock解决实际问题。
208

被折叠的 条评论
为什么被折叠?



