[Java并发编程实战]显示锁Lockh和ReentrantLock

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

老骥伏枥,志在千里;烈士暮年,壮心不已。———曹操《龟虽寿》
意思是老的千里马虽然伏在马槽旁,雄心壮志仍是驰骋千里;壮志凌云的人士即便到了晚年,奋发思进的心也永不止息。

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");
            }
        }   
    }

}

执行结果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值