Java读-写锁

显示锁

在java5.0之前,在协调共享对象访问时可以使用的机制只有synchronized和volatile。java5.0增加了一种新的机制:ReentrantLock。ReentrantLock并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。与内置锁不同的是Lock提供了一个无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁都是显示的。在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();
}

读写锁

ReetrantLock实现了一种标准的互斥锁:每次最多只有一个线程能持有ReetrantLock.对于维护数据完整性来说,互斥是一种过于强硬的加锁规则,因此也就不必要的限制了并发性。在许多情况下数据结构上的操作都是“读操作”。如果能够放宽加锁需求,允许多个执行读操作的线程同时访问数据结构,那么将提升程序的性能。只要每个线程都能确保读到最新的数据,并且在读取数据时不会有其它的线程修改数据,那么就不会发生问题。在这种情况下就可以使用读写锁:一个资源可以被多个读操作访问,或者被一个写操作访问,但是两者不能同时进行。

/*ReadWriteLock接口*/
public interface ReadWriteLock{
    Lock readLock();
    Lock writeLock();
}

在读锁和写锁之间的交互可以采用多种实现方式。ReadWriteLock中的一些可选实现包括:

  • 释放优先。当一个写入操作释放写入锁时,并且队列中同时存在读线程和写线程,那么应该优先选择哪一个线程。
  • 读线程插队。如果锁是由读线程持有,但是写线程还在等待,是否允许新到的读线程获得访问权,还是应在写线程后面等待?若允许的话可以提高并发性但是可能造成写线程的饥饿。
  • 重入性。读取锁和写入锁是否可重入。
  • 降级和升级。若一个线程持有写锁可否在继续持有写锁的状态下获取读锁?这可能会使写锁“降级”为读锁。读锁是否优先于其它正在等待的读线程和写线程而升级为一个写锁?在大多数读写锁实现中不支持“升级”,因为这样容易死锁(两个读线程试图同时升级为写锁,那么二者都不会释放写锁)。

ReentrantReadWriteLock为这两种锁都提供了可重入的语义。与ReentrantLock类似,ReentrantReadWriteLock在构造时也可以选择是一个非公平的锁(默认)还是一个公平的锁。在公平锁中,等待时间长的线程优先获得锁。如果这个锁由读线程持有,而另一线程请求写锁,那么其他线程不能获得读锁直到写锁被请求并使用后释放掉。在非公平的锁中,线程获得锁的顺序是不确定的。写线程降级为读线程是可以的,反过来不可以(会死锁的)。

与ReentrantLock类似的是,ReentrantReadWriteLock中的写入锁只能有一个持有者,并且只能有获得的线程释放。在Java5.0中的读锁更类似于Semaphore而不是锁,它只是维护活跃的读线程数量,而不考虑他们的标识,这样无法区别一个线程是是首次请求还是可重入请求,从而使公平的读写锁发生死锁。java6中修改了这个实现:记录哪些线程已经获得了读锁。

/*用读写锁来包装map*/
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();
        }
    }
    // 对remove(),putAll(),clear()等方法执行相同的操作
    public V get(Object key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }
    // 对其它只读的map方法执行相同的操作
}

当锁的持有时间较长并且大部分是读操作时那么读写锁能很好的提高并发性。上面程序中使用了ReentrantReadWriteLock来包装Map,从而使它能够在多个读线程间被共享。在现实中ConcurrentHashMap的性能已经很好了,如果是需要一个并发的基于散列的映射,那么使用ConcurrentHashMap来代替这种方法。但如果需要对另一种Map实现(如LinkedHashMap)提供并发性更高的访问,那么可以使用这种技术。


参考:

- 现代操作系统

- Java并发编程实战

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值