当线程请求内部锁时,如果锁已经被占用,则请求线程必须无条件等待,这往往会造成很多奇怪问题,相互等待是造成死锁的重要原因之一,著名的哲学家就餐问题就是个典型的案例。新的Lock锁提供了尝试获取锁失败自动放弃的方法tryLock(),具有更完善的错误恢复机制。


boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;


Lock接口定义了两个重载tryLock()函数,第一个函数表示如果锁可用则立即获得锁并返回true,如果请求的锁当前不可用则立即放弃并返回false;第二个函数表示如果请求的锁当前可用则立即获得锁并返回true,如果锁不可用则等待指定的时间,在等待期间,线程可能被中断,可能获得锁,如果等待时间结束还未获取到锁则自动放弃并返回false,明显相对第一个函数提供了更多的灵活性。


当然我们还可以自己利用循环轮询调用tryLock(),以实现轮询锁等,实现更多的灵活机制,这就是Lock相对内部锁的好处,特别是当线程需要同时获取多把不同锁时,tryLock()大有用处。


举一个实际简单应用场景,假设我们有个定时任务,定时清理垃圾,但每次清理垃圾耗费的时间可能不同,如果新任务发现上个任务还没执行完就自己结束,因为不需要这次任务。其实ScheduledExecutorService.scheduleAtFixedRate()函数就提供这样的功能,每个定时任务都是等上个任务完成才开始执行。


import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestTryLock {
    private Lock mLock;
    private volatile boolean isTimeoutEnabled;
                                                     
    public TestTryLock(){
        this.mLock = new ReentrantLock();
        this.isTimeoutEnabled = false;
    }
                                                     
    public void clearRubbish() throws InterruptedException{
        if(mLock.tryLock()){
            try{
                System.out.println(Thread.currentThread().getName() + ":get the lock successfully.");
                int estTime = (int) (10 * Math.random());
                System.out.println(Thread.currentThread().getName() + ":estimate time for finish job:" + estTime);
                TimeUnit.SECONDS.sleep(estTime);
            }
            finally{
                mLock.unlock();
            }          
        }
        else{
            System.out.println(Thread.currentThread().getName() + ":failed to get the lock.");
        }      
    }
                                                     
    public void clearRubbishWithTimeout() throws InterruptedException{
        if(mLock.tryLock(2, TimeUnit.SECONDS)){
            try{
                System.out.println(Thread.currentThread().getName() + ":get the lock successfully by waiting 2 seconds.");
                int estTime = (int) (10 * Math.random());
                System.out.println(Thread.currentThread().getName() + ":estimate time for finish job:" + estTime);
                TimeUnit.SECONDS.sleep(estTime);
            }
            finally{
                mLock.unlock();
            }          
        }
        else{
            System.out.println(Thread.currentThread().getName() + ":failed to get the lock after waiting 2 seconds.");
        }      
    }
                                                     
    private class Worker implements Runnable{
        @Override
        public void run() {
            try {
                if(isTimeoutEnabled){
                    clearRubbishWithTimeout();
                }
                else{
                    clearRubbish();
                }  
                                                                 
                isTimeoutEnabled = !isTimeoutEnabled;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }      
    }
                                                     
    private class TimerWorker implements Runnable{
        @Override
        public void run() {
            new Thread(new Worker()).start();
        }      
    }
    public static void main(String[] args) {
        TestTryLock testTryLock = new TestTryLock();
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(testTryLock. new TimerWorker(), 0, 3, TimeUnit.SECONDS);       
    }
}