Java并发——读写锁

2017-08-01  本文已影响0人  Q南南南Q

读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读
线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。Java并发包提供读写锁的实现是ReentrantReadWriteLock,它提供的特性如表所示:

ReentrantReadWriteLock的特性

读写锁的实现分析

1 读写状态的设计

读写锁同样依赖自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态。读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键。

如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写,划分方式如图所示:

读写锁状态的划分方式

读写锁是如何迅速确定读和写各自的状态呢?答案是通过位运算。假设当前同步状态值为S,写状态等于S&0x0000FFFF(将高16位全部抹去),读状态等于S>>>16(无符号补0右移16位)。当写状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16),也就是S+0x00010000。

根据状态的划分能得出一个推论:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取。

2 写锁的获取与释放

写锁是一个支持重进入的排它锁,获取情况有两种

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // 存在读锁或者当前获取线程不是已经获取写锁的线程
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 增加写锁状态
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) {
        return false;
    }
    setExclusiveOwnerThread(current);
    return true;
}

3 读锁的获取与释放

读锁是一个支持重进入的共享锁,它能够被多个线程同时获取。

protected final int tryAcquireShared(int unused) {
    for (;;) {
        int c = getState();
        int nextc = c + (1 << 16);
        if (nextc < c)
            throw new Error("Maximum lock count exceeded");
        // 如果当前线程不是获取写锁的线程,则获取读锁失败
        if (exclusiveCount(c) != 0 && owner != Thread.currentThread())
            return -1;
        // 如果当前线程是获取写锁的线程(锁降级) 
        // 或写锁未被线程获取,则获取读锁
        if (compareAndSetState(c, nextc))
            return 1;
    }
}

4 锁降级

如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。

锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

那么锁降级的设计的目的是什么呢?为何要在拥有写锁的前提下去获取读锁?
通过查看一些文章,写一下自己的理解:

锁降级的目的其实是为了让线程对数据变化敏感,如果先释放写锁,再获取读锁,可能在获取之前,会有其他线程获取到写锁,阻塞读锁的获取,就无法感知数据变化了。所以需要先hold住写锁,保证数据无变化,获取读锁,然后再释放写锁。

例如有多个线程对同一块数据区域data进行读写操作,要求对每次数据的更改敏感。假设t1时刻data区域被写线程将状态s0更改为s1,更改完后若直接释放锁,那么可能会有其他线程获取写锁,将data区域的状态从s1更改为s2,这样一来整个过程就无法感知到data区域的s1状态。

如果采用了锁降级,那么获取写锁的线程t将data区域状态更改为s1后便持有读锁,那么其它想获取写锁的线程将会阻塞,直到线程t将读锁释放,那么这个过程中将会感知到data区域的s1状态。

锁降级对比
上一篇 下一篇

猜你喜欢

热点阅读