您的位置:首页 > 编程语言 > Java开发

JAVA多线程之——读写锁 ReentrantReadWriteLock

2017-04-02 23:17 701 查看

ReentrantReadWriteLock

锁的类型有很多,前面学习了阻塞锁、互斥锁、自旋锁等。今天学习读写锁。所谓读写锁就是维护了一个读锁和写锁。但是读锁和写锁互斥、写锁和写锁互斥。读锁和读锁不互斥。既允许多个读锁同时读。但是同时间只有一个写锁写。读写锁也是可重入锁。

根据JDK文档描述,所以读写锁以下几个特点:

1. 读写锁对于读锁与写锁的获取顺序不会干涉。

2. 非公平模式下可能会导致饥饿状态。也就是说可能会无限推迟一个读锁或者写锁线程获取锁。

3. 公平模式下当锁被释放时候,会为等待最长时间的写锁分配锁,如果有线程的等待时间大于所有写线程的等待时间,那就分配这些线程读锁。

4. 读写锁是可以重入的。但是要切记其中的锁互斥问题。也就是读锁要重入,就必须所有的写锁都已经释放。写锁可以重入读锁,但是读锁不能重入写锁。

5. 重入还允许从写入锁降级为读取锁,实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。

6. 写入锁提供了一个 Condition 实现,对于写入锁来说,该实现的行为与 ReentrantLock.newCondition() 提供的Condition 实现对 ReentrantLock 所做的行为相同。当然,此 Condition 只能用于写入锁。

读取锁不支持 Condition,readLock().newCondition() 会抛出 UnsupportedOperationException。

7. 此类支持一些确定是读取锁还是写入锁的方法。这些方法设计用于监视系统状态,而不是同步控制。

先来看一下部分源码:

/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;


代码中可以看到,读写锁中包含了一个读锁和一个写锁。

public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;

/**
* Constructor for use by subclasses
*
* @param lock the outer lock object
* @throws NullPointerException if the lock is null
*/
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
}


这是写锁的一部分源码,可以看出写锁实现的就是Lock。通过前面学习了解了,Lock的一个实现原理是基于一个内部类Sync 实现AQS的方式。而AQS底层维护的是一个修改过的CLH队列。

AQS如何用一个状态同时表示读锁和写锁?

/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

/** Returns the number of shared holds represented in count  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }


看注释可以知道,一个整型32位,然后将它分为两部分,高16位表示读锁的线程数量,低16位表示写锁的线程数量。

如何统计每个线程的重入数?

abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* 每个线程特定的 read 持有计数。存放在ThreadLocal,不需要是线程安全的。
*/
static final class HoldCounter {
int count = 0;
// 使用id而不是引用是为了避免保留垃圾。
final long tid = Thread.currentThread().getId();
}
/**
* 如果ThreadLocal没有当前线程的计数,则new一个,再放进ThreadLocal里。
* 可以直接调用 get。
* */
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
/**
* 保存当前线程重入读锁的次数的容器。在读锁重入次数为 0 时移除。
*/
private transient ThreadLocalHoldCounter readHolds;
}


分析源码可以看出,每个读线程都维护自己一个重入数的一个ThreadLocal变量readHolds。这样就能各自统计自己的重入次数。

写一段小代码来实战一下读写锁的用法。JDK文档中也有一个经典的例子。

public class ReadWriteLockTest {
p
4000
rivate static Map<String, Object>  map = new HashMap<String,Object>();
private static Object obj  = null;
private static final ReentrantReadWriteLock  lock = new ReentrantReadWriteLock();
private static  Lock  readLock = lock.readLock();
private static  Lock  writeLock = lock.writeLock();
public static Object get(String key) {
try{
readLock.lock();
obj = map.get(key);
if(null == obj) {
try{
readLock.unlock();
writeLock.lock();
if(null == obj) {
obj = "abc";
map.put(key,obj);
}

}finally{
readLock.lock();
writeLock.unlock();
}
}

}finally{
readLock.unlock();

}
if( null == obj) {
readLock.unlock();
writeLock.lock();
obj = map.get(key);
}
return obj;
}

}


基本就是实现一个缓存的机制。多个线程可以去读取。每次只有一个线程可以写入。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程