读写锁

Long

在前面的文章中我们介绍过两种锁:内置锁(synchronized)和显式锁(ReentrantLock)。这两种锁都是独占锁,也就是说获取到这个锁之后其它线程再想获取这个锁必须等当前线程释放这个锁。有些时候线程多数情况下都是读取数据的值,而不是修改这个数据,但是读取数据的线程之间是可以并发读取的,如果使用独占锁会限制系统的性能,由此读写锁应运而生。

这里举个例子解释一下读写锁的互斥问题,假如有个黑板,上面写着一个“小”字,学生之间可以一起看到这个字,不存在互斥的情况,因此读操作和读操作之间是可以并行的。老师说要再写一个“大”字,在老师停笔之前学生们看到的字都是错的,如果提前读可能会看成是个“一”字,因此读和写之间的操作是互斥的。至于写和写操作互斥就比较好理解了,这里就不赘述了。

用法

Java中读写锁的接口是ReadWriteLock,实现类是ReentrantReadWriteLock。ReadWriteLock接口只有两个方法:readLock()和writeLock(),两个方法分别返回读锁和写锁对象,返回值类型是Lock类型,因此返回对象的用法和ReentrantLock的用法相同。下面给出一个读写锁的典型用法:

class MyList<T> implements List<T>{
    private List<T> list = new ArrayList<T>();
    private ReadWriteLock rwl = new ReentrantReadWriteLock();
    private Lock readLock = rwl.readLock();
    private Lock writeLock = rwl.writeLock();
    public T get(int index) {
	readLock.lock();
	try {
	    return list.get(index);
	}
	finally {
	    readLock.unlock();
	}
    }
    public boolean add(T e) {
	writeLock.lock();
 	try {
	    return list.add(e);
	}
	finally {
	    writeLock.unlock();
	}
    }
    //...此处略去N个方法
}

在这个例子中,读取list中的元素获取的是读锁,向list中添加元素获取的是写锁。由于读锁之间是不互斥的,因此如果对list读取的操作比较多,则相比于使用独占锁来说系统的性能会有很大提升;如果对list写入的操作比较多,则系统性能的提升就十分有限,因为写锁本身就是独占锁,而且还要判断读锁的状态,导致写锁的性能没有ReentrantLock好。

此外ReentrantReadWriteLock支持选择锁的公平性,它有两个构造函数ReentrantReadWriteLock()和ReentrantReadWriteLock(boolean fair),无参数的构造函数使用的是非公平锁,我们也可以使用带参数的构造函数指定锁为公平锁。

属性

读比写优先?假设一种情况,一个线程当前获取了读锁,正在读取数据,有一个写线程在排队队列中等待,此时有一个读线程尝试获取读锁。这种情况下有两种选择:1.让读线程排到队尾。2.让读线程立即获取读锁,因为读线程可以和已经持有锁的线程共享读锁。

当线程已经是否可重入?获取了锁,是否允许这个线程再次获取。

是否允许锁降级?如果当前线程已经获取了写锁,此时是否允许线程再获取读锁。如果允许获取读锁,线程此时释放写锁就会导致由原来的写锁降级成读锁。

是否允许锁升级?如果当前线程已经获取了读锁,此时是否允许线程再获取写锁。如果允许获取写锁,锁就会有读锁升级成写锁。

1.读不比写优先,如果读比写优先可以在一定程度上提升性能,因为提升了程序的并发性,然而这样也会带来一个问题:写线程有饥饿的风险。在前面的文章中我们提到过不公平会导致饥饿的问题,这里的写线程饥饿也是一个道理。在性能和平稳运行之间我们只能选择平稳运行。

2.可以重入,如果不可以重入会导致一个线程自己与自己发生死锁,因为第二次获取锁的时候自己已经拥有了这个锁。

3.允许锁降级,因为当线程持有写锁的时候没有其它线程可以获取读写锁,因此再获取读锁是安全的。而此时再释放写锁就会降级为读锁,其它线程也可以获得读锁。

4.不允许锁升级,因为这会导致死锁,如果只有一个线程试图升级锁是没有问题的,可是只有一个线程毕竟是少数情况。试想如果两个线程同时尝试升级锁,两个线程都在等待对方释放读锁,它们之间就发生了死锁。

总结

当线程多数情况下是读取数据时,使用读写锁可以使线程并发读取数据,从而有效的提高系统的性能。但是如果写入的情况比较多则不适合使用读写锁,从性能上讲写锁的性能没有独占锁的性能好。因此是否使用读写锁还要根据系统逻辑的需求来确定。

https://zhuanlan.zhihu.com/p/42701772

原文地址:https://www.cnblogs.com/lovezbs/p/13951596.html