显示锁

来源:互联网 发布:vb控制台应用程序 编辑:程序博客网 时间:2024/06/07 20:45

在Java 5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile。Java 5.0新增了一种新的机制:ReentrantLock。与之前提到过的机制相反,ReentrantLock并不是一种替代内置锁的机制,而是当内置所机制不适用时,所用一种可选择的高级功能。

1. Lock与ReentrantLock

下列的程序清单给出Lock接口中定义的一组抽象的加锁操作。与内置锁机制不同的是,Lock提供了一种无条件的。可轮训的、定时的以及可中断的锁获取操作,所有加锁和解锁的操作都是显示的。

public interface Lock{    void lock();    void lockInterrruptibly() throws InterruptedException;    boolean tryLock();    boolean tryLock(long timeout, TimeUnit unit);    void unlock();    Condition newCondition();}

下列的程序清单给出了Lock接口的标准使用形式。这种形式比内置锁复杂一些。必须在finally块中释放锁。否则,如果在被保护的代码块中抛出了异常,那么这个所永远都无法被释放。

Lock lock = new ReentrantLock();...lock.lock();    try{        //doSomething    }finally{        lock.unlock();    }

2. 轮询锁与定时锁

可定时的与可轮询的锁获取模式是由tryLock方法实现的。在内置锁中,死锁是一个严重的问题,恢复程序的唯一办法就是重新启动程序,而防止死锁的唯一方法就是在构造程序时避免出现不一致的锁顺序。可定时的与可轮询的锁提供了另一种方式防止死锁的发生。

下列的程序清单采用了另一种方式来解决动态顺序的问题。使用tryLock来获取两个锁。如果不能同时获得,那么就回退并重新尝试。

public boolean transferMoney(Accont fromAcct,                              Account toAcct,                             DollarAmount amount,                             long timeout,                              TimeUnit unit)throws InterruptedException{    long stopTime = System.nanoTime + unit.toNanos(timeount);    while(true){            if(fromAcct.lock.tryLock()){            try{                if(toAcct.lock.tryLock()){                    try{                        fromAcct.debit(amount);                        toAcct.credit(amount);                        return true;                    }finally{                        toAcct.lock.unlock();                    }                }            }finally{                fromAcct.lock.unlock();            }        }        if(System.nanoTime() < stopTime){            return false;        }    }   }

在实现具有时间限制的操作时,定时锁同样非常有用。当带有时间限制的操作中调用了一个阻塞方法时,它能根据剩余时间来提供一个实现。如果在规定时间内不能给出结果,那么就会使程序提前结束。当使用内置锁时,在开始请求锁后,这个请求无法取消,因此内置锁很难实现带有时间限制的操作。下列的程序清单给出使用示例。

public boolean trySendOnSharedLine(String message,                                   long timeout,                                   TimeUnit unit)throws InterruptedException{    long nanosToLock = unit.toNanos(timeount) - estimedNanosToSend(message);    if(!lock.tryLock(nanosToLock, NANOSENDSECONDS){        return false;    }    try{        retrun sendOnSharedLine(message);    }finally{        lock.unlock();    }}

4. 可中断的锁获取操作

lock.InterruptedException方法能够在获得锁的同事保持对中断的响应,并且由于它包含在Lock中,因此无须创建其他类型的不可中断阻塞机制。定时的tryLock同样能响应中断,因此当需要实现一个定时的可中断的锁获取操作时,可以使用tryLock方法。程序清单如下。

public boolean sendOnSharedLine(String message)throws InterruptedException{    lock.lockInterruptibly();    try{        //doSomething    }finally{        lock.unlock();    }}

5. 公平性

在ReentrantLock的构造函数中提供了两种公平性选择:创建一个非公平性(默认)或者一个公平的锁。在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许“插队”:当一个线程请求一个非公平锁时,如果在发出请求的同时该锁的状态变为可用,那么线程将跳过队列中所有的等待线程并获得这个锁。

6. 读写锁

若每个线程都能确保读到最新的数据,并且在读取数据时不会有其他的线程修改数据,那么就不会发生问题,这种情况下就可以使用读写锁,一个资源可以被多个读操作访问,或者被一个写操作访问。但两者不能同时进行。在ReadWriteLock中暴露了两个Lock对象。其中一个用于读操作,而另一个用于写操作。要读取由ReadWriteLock保护的数据,必须首先要获得读取锁。当需要修改数据时,必须首先获得写入锁。尽管这连个锁看上去是彼此独立的。但读取锁和写入锁只是读-写对象的不同视图。

public interface ReadWriteLock{    Lock readLock();    Lock writeLock();}

在ReentrantReadWriteLock中,如果锁被读线程所只有,而另一个线程请求写入锁,那么其他读线程都不能获得读取锁,直到写线程释放锁。下列的程序简单展示了ReentrantReadWriteLock的使用。

public class ReadWriteMap<K, V>{    private final Map<K, V> map;    private final ReadWriteLock lock = new ReentrantReadWriteLock();    private final Lock r = lock.readLock();    private final Lock w = lock.writeLock();    public ReadWriteMap(Map<K, V> map){        this.map = map;    }    public V put(K key, V value){        w.lock();        try{            return map.put(key, value);        }finally{            w.unlock();        }    }    public V get(K key){        r.lock();        try{            return map.get(key);        }finally{            r.unlock();        }    }}