java源码分析---ReadWriteLock(上)

**

java源码分析—ReadWriteLock(上)

**
1. 体系结构
以下是ReadWriteLock接口的源码

/**
 * A {@code ReadWriteLock} maintains a pair of associated {@link
 * Lock locks}, one for read-only operations and one for writing.
 * The {@link #readLock read lock} may be held simultaneously by
 * multiple reader threads, so long as there are no writers.  The
 * {@link #writeLock write lock} is exclusive.
 *
 * <p>All {@code ReadWriteLock} implementations must guarantee that
 * the memory synchronization effects of {@code writeLock} operations
 * (as specified in the {@link Lock} interface) also hold with respect
 * to the associated {@code readLock}. That is, a thread successfully
 * acquiring the read lock will see all updates made upon previous
 * release of the write lock.
 *
 * <p>A read-write lock allows for a greater level of concurrency in
 * accessing shared data than that permitted by a mutual exclusion lock.
 * It exploits the fact that while only a single thread at a time (a
 * <em>writer</em> thread) can modify the shared data, in many cases any
 * number of threads can concurrently read the data (hence <em>reader</em>
 * threads).
 * In theory, the increase in concurrency permitted by the use of a read-write
 * lock will lead to performance improvements over the use of a mutual
 * exclusion lock. In practice this increase in concurrency will only be fully
 * realized on a multi-processor, and then only if the access patterns for
 * the shared data are suitable.
 *
 * <p>Whether or not a read-write lock will improve performance over the use
 * of a mutual exclusion lock depends on the frequency that the data is
 * read compared to being modified, the duration of the read and write
 * operations, and the contention for the data - that is, the number of
 * threads that will try to read or write the data at the same time.
 * For example, a collection that is initially populated with data and
 * thereafter infrequently modified, while being frequently searched
 * (such as a directory of some kind) is an ideal candidate for the use of
 * a read-write lock. However, if updates become frequent then the data
 * spends most of its time being exclusively locked and there is little, if any
 * increase in concurrency. Further, if the read operations are too short
 * the overhead of the read-write lock implementation (which is inherently
 * more complex than a mutual exclusion lock) can dominate the execution
 * cost, particularly as many read-write lock implementations still serialize
 * all threads through a small section of code. Ultimately, only profiling
 * and measurement will establish whether the use of a read-write lock is
 * suitable for your application.
 *
 *
 * <p>Although the basic operation of a read-write lock is straight-forward,
 * there are many policy decisions that an implementation must make, which
 * may affect the effectiveness of the read-write lock in a given application.
 * Examples of these policies include:
 * <ul>
 * <li>Determining whether to grant the read lock or the write lock, when
 * both readers and writers are waiting, at the time that a writer releases
 * the write lock. Writer preference is common, as writes are expected to be
 * short and infrequent. Reader preference is less common as it can lead to
 * lengthy delays for a write if the readers are frequent and long-lived as
 * expected. Fair, or &quot;in-order&quot; implementations are also possible.
 *
 * <li>Determining whether readers that request the read lock while a
 * reader is active and a writer is waiting, are granted the read lock.
 * Preference to the reader can delay the writer indefinitely, while
 * preference to the writer can reduce the potential for concurrency.
 *
 * <li>Determining whether the locks are reentrant: can a thread with the
 * write lock reacquire it? Can it acquire a read lock while holding the
 * write lock? Is the read lock itself reentrant?
 *
 * <li>Can the write lock be downgraded to a read lock without allowing
 * an intervening writer? Can a read lock be upgraded to a write lock,
 * in preference to other waiting readers or writers?
 *
 * </ul>
 * You should consider all of these things when evaluating the suitability
 * of a given implementation for your application.
 *
 * @see ReentrantReadWriteLock
 * @see Lock
 * @see ReentrantLock
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

通过源码我们了解到ReadWriteLock接口仅有两个方法(两种锁),一个是readLock只读锁,另一个是writeLock写锁。
首先我们分析下ReadWriteLock的注释:
(1)ReadWriteLock拥有一对相关的锁,只读锁和写锁。在没有写的情况下,只读锁可以被多个读线程同时拥有。写锁具有排他性。
(2)所有ReadWriteLock的实现必须保证在写操作的情况下的内存同步,也就是说,一个线程成功地获取了读锁,将会看到在之前的写锁释放时所做的所有更新
(3)读写锁访问共享数据比互斥锁具有更高级别的并发性,这意味着在同一时刻只允许一个写线程修改数据,而允许多个读线程同时读取数据。
(4)从理论上讲,使用读写锁比互斥锁会在并发性上提供更高的性能。在实践中,并发性的增加只会在多处理器上完全实现,并且只有当共享数据的访问模式适合时才会实现。

2. 读写锁在java中的实现
2.1 简单实现一个读写锁
先让我们对读写访问资源的条件做个概述:
读取:没有线程正在做写操作,且没有线程在请求写操作。
写入:没有线程正在做读写操作。
如果某个线程想要读取资源,只要没有线程正在对该资源进行写操作且没有线程请求对该资源的写操作即可。我们假设对写操作的请求比对读操作的请求更重要,就要提升写请求的优先级。此外,如果读操作发生的比较频繁,我们又没有提升写操作的优先级,那么就会产生“饥饿”现象。请求写操作的线程会一直阻塞,直到所有的读线程都从ReadWriteLock上解锁了。如果一直保证新线程的读操作权限,那么等待写操作的线程就会一直阻塞下去,结果就是发生“饥饿”。因此,只有当没有线程正在锁住ReadWriteLock进行写操作,且没有线程请求该锁准备执行写操作时,才能保证读操作继续。
当其它线程没有对共享资源进行读操作或者写操作时,某个线程就有可能获得该共享资源的写锁,进而对共享资源进行写操作。有多少线程请求了写锁以及以何种顺序请求写锁并不重要,除非你想保证写锁请求的公平性。
按照上面的叙述,简单的实现出一个读/写锁,代码如下

public class ReadWriteLock {
    private int readers=0;
    private int writers=0;
    private int requestWrite=0;
    public synchronized void readLock()
    {
        try {
            if(writers>0 && requestWrite >0)
            {
                wait();
            }
            readers++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void writerLock()
    {
        try {
            requestWrite++;
            if(readers>0 && writers>0)
            {
                wait();
            }
            requestWrite--;
            writers++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void unLockRead()
    {
        readers--;
        notifyAll();
    }
    public void unLockWrite()
    {
        writers--;
        notifyAll();
    }
}

(1)在上面我们自己实现的ReadWriteLock类中,分别有两个方法用来获取和释放读锁和写锁。
读锁的实现在readLock()中,只要没有线程拥有写锁(writers==0),且没有线程在请求写锁(writeRequests ==0),所有想获得读锁的线程都能成功获取。
(2)写锁的实现在writerLock()中,当一个线程想获得写锁的时候,首先会把写锁请求数加1(writeRequests++),然后再去判断是否能够真能获得写锁,当没有线程持有读锁(readers==0 ),且没有线程持有写锁(writers==0)时就能获得写锁。有多少线程在请求写锁并无关系。
需要注意的是,在两个释放锁的方法(unlockRead,unlockWrite)中,都调用了notifyAll方法,而不是notify。要解释这个原因,我们可以想象下面一种情形:
如果有线程在等待获取读锁,同时又有线程在等待获取写锁。如果这时其中一个等待读锁的线程被notify方法唤醒,但因为此时仍有请求写锁的线程存在(writeRequests>0),所以被唤醒的线程会再次进入阻塞状态。然而,等待写锁的线程一个也没被唤醒,就像什么也没发生过一样(译者注:信号丢失现象)。如果用的是notifyAll方法,所有的线程都会被唤醒,然后判断能否获得其请求的锁。
用notifyAll还有一个好处。如果有多个读线程在等待读锁且没有线程在等待写锁时,调用unlockWrite()后,所有等待读锁的线程都能立马成功获取读锁 —— 而不是一次只允许一个。
读/写锁的重入

上面实现的读/写锁(ReadWriteLock) 是不可重入的,当一个已经持有写锁的线程再次请求写锁时,就会被阻塞。原因是已经有一个写线程了——就是它自己。为了让ReadWriteLock可重入,需要对它做一些改进。下面会分别处理读锁的重入和写锁的重入。

2.2 读锁重入
为了让我们实现的ReadWriteLock的读锁可重入,我们要先为读锁重入建立规则:
要保证某个线程中的读锁可重入,要么满足获取读锁的条件(没有写或写请求),要么已经持有读锁(不管是否有写请求)。
要确定一个线程是否已经持有读锁,可以用一个map来存储已经持有读锁的线程以及对应线程获取读锁的次数,当需要判断某个线程能否获得读锁时,就利用map中存储的数据进行判断。下面是方法readLock和unLockRead修改后的的代码:

    public synchronized void readLock() {
        try {
            Thread currentThread = Thread.currentThread();
            if (!canReadAccess(currentThread)) {
                wait();
            }
            readingThreads.put(currentThread, getReadAccessCount(currentThread) + 1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void unLockRead() {
        Thread callingThread = Thread.currentThread();
        int accessCount = getReadAccessCount(callingThread);
        if (accessCount == 1) {
            readingThreads.remove(callingThread);
        } else {
            readingThreads.put(callingThread, (accessCount - 1));
        }
        notifyAll();
    }

    private boolean canReadAccess(Thread thread) {
        if (writers > 0) return false;
        if (isReader(thread)) return true;
        if (requestWrite > 0) return false;
        return true;
    }

    private boolean isReader(Thread thread) {
        return readingThreads.get(thread) != null;
    }

    private int getReadAccessCount(Thread thread) {
        Integer accessCount = readingThreads.get(thread);
        if (accessCount == null) return 0;
        return accessCount.intValue();
    }

2.3 写锁重入

public synchronized void writerLock() {
        try {
            Thread currentThread = Thread.currentThread();
            requestWrite++;
            if (!canWriteAccess(currentThread)) {
                wait();
            }
            requestWrite--;
            writers++;
            writingThread = currentThread;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void unLockWrite() {
        writers--;
        if (writers == 0) {
            writingThread = null;
        }
        notifyAll();
    }
    private boolean canWriteAccess(Thread thread) {
        if (hasReaders()) return false;
        if (writingThread == null) return true;
        if (!isWriter(thread)) return false;
        return true;
    }

    private boolean hasReaders() {
        return readingThreads.size() > 0;
    }

    private boolean isWriter(Thread thread) {
        return writingThread == thread;
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值