Java中如何实现一个"读写锁"呢?
在一些java的业务场景中,多线程对共享资源的读写占比有时候不均衡,如:读操作非常频繁,而写操作非常少, 那么有时候希望在没有写操作时,多个线程可同时读一个共享资源, 如果一个线程希望对共享资源进行写操作时,则此时就不应该有其它的线程对共享资源进行操作
读/写锁的Java实现
- 读取:没有线程正在做写操作,且没有线程在请求写操作
- 写入:没有线程正在做读写操作
注意事项: 当写操作比读操作更重要时,此时我们可通过提高写的优先级 因为如果写操作非常频繁,写操作未提升优先级,则会出现“饥饿”现象(写操作一直处于阻塞状态)例:
public class ReadWriteLock{ private int readers = 0; private int writers = 0; private int writeRequests = 0; public synchronized void lockRead() throws InterruptedException{ while(writers > 0 || writeRequests > 0){ wait(); } readers++; } public synchronized void unlockRead(){ readers--; notifyAll(); } public synchronized void lockWrite() throws InterruptedException{ writeRequests++; while(readers > 0 || writers > 0){ wait(); } writeRequests--; writers++; } public synchronized void unlockWrite() throws InterruptedException{ writers--; notifyAll(); } }在ReadWriteLock类中,读锁和写锁各有一个获取锁和释放锁的方法
读锁的实现在lockRead()中
当没有线程拥有写锁(writers==0),且没有线程在请求写锁(writeRequests ==0),所有想获得读锁的线程都能成功获取。
写锁的实现在lockWrite()中,当一个线程想获得写锁的时候
首先会把写锁请求数加1(writeRequests++),然后再去判断是否能够真能获得写锁,当没有线程持有读锁(readers==0),且没有线程持有写锁(writers==0)时就能获得写锁
需要注意的是,在两个释放锁的方法(unlockRead,unlockWrite)中,都调用了notifyAll方法,而不是notify
如果有线程在等待获取读锁,同时又有线程在等待获取写锁 如果这时其中一个等待读锁的线程被notify方法唤醒,但因为此时仍有请求写锁的线程存在(writeRequests>0),所以被唤醒的线程会再次进入阻塞状态。 然而,等待写锁的线程一个也没被唤醒,就像什么也没发生过一样(译者注:信号丢失现象)。如果用的是notifyAll方法,所有的线程都会被唤醒,然后判断能否获得其请求的锁。 ----------------------------------------------------------------------------------------------------------------------------------------------------------------- 用notifyAll还有一个好处。如果有多个读线程在等待读锁且没有线程在等待写锁时,调用unlockWrite()后,所有等待读锁的线程都能立马成功获取读锁----而不是一次只允许一个
读/写锁的重入
上面实现的读/写锁(ReadWriteLock)是不可重入的,当一个已经持有写锁的线程再次请求写锁时,就会被阻塞。原因是已经有一个写线程了——就是它自己Thread 1 获得了读锁 Thread 2 请求写锁,但因为Thread 1 持有了读锁,所以写锁请求被阻塞。 Thread 1 再想请求一次读锁,但因为Thread 2处于请求写锁的状态,所以想再次获取读锁也会被阻塞。为了让ReadWriteLock可重入,需做相应的改进
读锁重入
为了让ReadWriteLock的读锁可重入,我们要先为读锁重入建立规则实现思路: 使用一个map存储已持有锁的线程及对应线程获取读锁的次数, 然后读map中的信息,进行相应的判断例:
public class ReadWriteLock{ private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>(); private int writers = 0; private int writeRequests = 0; public synchronized void lockRead() throws InterruptedException{ Thread callingThread = Thread.currentThread(); while(! canGrantReadAccess(callingThread)){ wait(); } readingThreads.put(callingThread, (getAccessCount(callingThread) + 1)); } public synchronized void unlockRead(){ Thread callingThread = Thread.currentThread(); int accessCount = getAccessCount(callingThread); if(accessCount == 1) { readingThreads.remove(callingThread); } else { readingThreads.put(callingThread, (accessCount -1)); } notifyAll(); } private boolean canGrantReadAccess(Thread callingThread){ if(writers > 0) return false; if(isReader(callingThread) return true; if(writeRequests > 0) return false; return true; } private int getReadAccessCount(Thread callingThread){ Integer accessCount = readingThreads.get(callingThread); if(accessCount == null) return 0; return accessCount.intValue(); } private boolean isReader(Thread callingThread){ return readingThreads.get(callingThread) != null; } }
写锁重入
当一个线程已经持有写锁,才允许写锁重入(再次获得写锁)public class ReadWriteLock{ private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>(); private int writeAccesses = 0; private int writeRequests = 0; private Thread writingThread = null; public synchronized void lockWrite() throws InterruptedException{ writeRequests++; Thread callingThread = Thread.currentThread(); while(!canGrantWriteAccess(callingThread)){ wait(); } writeRequests--; writeAccesses++; writingThread = callingThread; } public synchronized void unlockWrite() throws InterruptedException{ writeAccesses--; if(writeAccesses == 0){ writingThread = null; } notifyAll(); } private boolean canGrantWriteAccess(Thread callingThread){ if(hasReaders()) return false; if(writingThread == null) return true; if(!isWriter(callingThread)) return false; return true; } private boolean hasReaders(){ return readingThreads.size() > 0; } private boolean isWriter(Thread callingThread){ return writingThread == callingThread; } }读锁升级到写锁
public class ReadWriteLock{ private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>(); private int writeAccesses = 0; private int writeRequests = 0; private Thread writingThread = null; public synchronized void lockWrite() throws InterruptedException{ writeRequests++; Thread callingThread = Thread.currentThread(); while(!canGrantWriteAccess(callingThread)){ wait(); } writeRequests--; writeAccesses++; writingThread = callingThread; } public synchronized void unlockWrite() throws InterruptedException{ writeAccesses--; if(writeAccesses == 0){ writingThread = null; } notifyAll(); } private boolean canGrantWriteAccess(Thread callingThread){ if(isOnlyReader(callingThread)) return true; if(hasReaders()) return false; if(writingThread == null) return true; if(!isWriter(callingThread)) return false; return true; } private boolean hasReaders(){ return readingThreads.size() > 0; } private boolean isWriter(Thread callingThread){ return writingThread == callingThread; } private boolean isOnlyReader(Thread thread){ return readers == 1 && readingThreads.get(callingThread) != null; } }写锁降级到读锁
public class ReadWriteLock{ private boolean canGrantReadAccess(Thread callingThread){ if(isWriter(callingThread)) return true; if(writingThread != null) return false; if(isReader(callingThread) return true; if(writeRequests > 0) return false; return true; } }可重入的ReadWriteLock的完整实现
public class ReadWriteLock{ private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>(); private int writeAccesses = 0; private int writeRequests = 0; private Thread writingThread = null; public synchronized void lockRead() throws InterruptedException{ Thread callingThread = Thread.currentThread(); while(! canGrantReadAccess(callingThread)){ wait(); } readingThreads.put(callingThread, (getReadAccessCount(callingThread) + 1)); } private boolean canGrantReadAccess(Thread callingThread){ if(isWriter(callingThread)) return true; if(hasWriter()) return false; if(isReader(callingThread)) return true; if(hasWriteRequests()) return false; return true; } public synchronized void unlockRead(){ Thread callingThread = Thread.currentThread(); if(!isReader(callingThread)){ throw new IllegalMonitorStateException( "Calling Thread does not" + " hold a read lock on this ReadWriteLock"); } int accessCount = getReadAccessCount(callingThread); if(accessCount == 1){ readingThreads.remove(callingThread); } else { readingThreads.put(callingThread, (accessCount -1)); } notifyAll(); } public synchronized void lockWrite() throws InterruptedException{ writeRequests++; Thread callingThread = Thread.currentThread(); while(!canGrantWriteAccess(callingThread)){ wait(); } writeRequests--; writeAccesses++; writingThread = callingThread; } public synchronized void unlockWrite() throws InterruptedException{ if(!isWriter(Thread.currentThread()){ throw new IllegalMonitorStateException( "Calling Thread does not" + " hold the write lock on this ReadWriteLock"); } writeAccesses--; if(writeAccesses == 0){ writingThread = null; } notifyAll(); } private boolean canGrantWriteAccess(Thread callingThread){ if(isOnlyReader(callingThread)) return true; if(hasReaders()) return false; if(writingThread == null) return true; if(!isWriter(callingThread)) return false; return true; } private int getReadAccessCount(Thread callingThread){ Integer accessCount = readingThreads.get(callingThread); if(accessCount == null) return 0; return accessCount.intValue(); } private boolean hasReaders(){ return readingThreads.size() > 0; } private boolean isReader(Thread callingThread){ return readingThreads.get(callingThread) != null; } private boolean isOnlyReader(Thread callingThread){ return readingThreads.size() == 1 && readingThreads.get(callingThread) != null; } private boolean hasWriter(){ return writingThread != null; } private boolean isWriter(Thread callingThread){ return writingThread == callingThread; } private boolean hasWriteRequests(){ return this.writeRequests > 0; } }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。