Lock和synchronized 同步代码块类似,是线程同步机制,但是Lock比synchronized 同步代码块更加灵活。Lock是通过synchronized关键字来实现的。从Java 5开始,java.util.concurrent.locks包含几个锁,下面通过自己实现的Lock来看看Lock的实现原理。
public class Lock{
private boolean isLocked = false;
public synchronized void lock()
throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}
上面的这个简单的锁,是一个不可重入的锁。
Java中synchronized修饰的同步代码块是可重入的,也就是说,当前进程获取到了同步监视器后,可以进入到其他同步代码块中。下面是一个例子:
public class Reentrant{
public synchronized outer(){
inner();
//inner 是同一个监视器this下的同步代码块,所以可以直接从outer中进入。
}
public synchronized inner(){
//do something
}
}
上面代码中当前进程获取到监视器时,可以直接由outer同步代码块进入到inner同步代码块。但是使用Lock时就会出现问题:
public class Reentrant2{
Lock lock = new Lock();
public outer(){
lock.lock();
inner();
lock.unlock();
}
public synchronized inner(){
lock.lock();
//do something
lock.unlock();
}
}
线程调用outer()将首先lock Lock实例。然后会调用 inner()。在该inner()方法内,线程将再次尝试lock Lock实例。这里会出现问题,因为Lock实例已被锁定在outer()方法中。看看Lock的代码实现:
public class Lock{
boolean isLocked = false;
public synchronized void lock()
throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
...
}
为了使Lock可重入做如下改变:
public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock() throws InterruptedException{
Thread callingThread = Thread.currentThread();
//如果是同一个线程调用可重入。
while(isLocked && lockedBy != callingThread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = callingThread;
}
public synchronized void unlock(){
if(Thread.curentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
...
}
上面的Lock支持同一个线程多次进入锁。
首先总结获取资源的reader 和writer权限的条件:
①reader权限 如果没有线程正在写入,并且没有线程请求写访问。
②writer权限 如果没有线程正在读取或写入。
如果一个线程想要读取资源,只要没有线程正在写,没有线程请求对资源的写入访问就可以了。通过优先处理写访问请求,我们假设写请求比读请求更重要。此外,如果读数是最常发生的情况,而且我们没有提高写入优先级,可能会出现饥饿。要求写访问的线程将被阻止,直到所有读者解锁ReadWriteLock。如果不断授予新线程读访问权限,则等待写入访问的线程将保持不间断地被阻止,导致饥饿。因此,如果线程当前没有锁定ReadWriteLock写入,或者请求它锁定写入,线程只能被授予读取访问权限。
考虑到这些规则下面是ReadWriteLock实现的实现:
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 有两个 lock 方法和两个 unlock 方法. 其中一个 lock 和unlock 方法对应read 权限另一个 lock 和unlock 方法对应 write 权限。
lockRead()中实现了读权限的控制. 当没有线程writer的时候,才可以进行读操作。
上面的ReadWriteLock是不可重入的,当一个线程获取到writer权限后,再次获取一个writer权限时或发生阻塞,考虑一下情形。
①Thread 1 获得read 权限。
②Thread 2 请求获得write 权限因为Thread 1 在读所以线程2会被阻塞。
③Thread 1 再次请求 read 权限(重新进入lock), 因为Thread 2 在请求writer所以Thread 1会被阻塞。
为了使 ReadWriteLock 对 readers 可重入先建立规则:当一个线程已经获取到read权限的时候可以重新获取reader权限。
为了确定一个线程是否已经获取到了读权限,可以用一个HashMap来保存线程和reader次数。
public class ReadWriteLock{
private Map readingThreads =
new HashMap();
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 readingThreads =
new HashMap();
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;
}
}
http://tutorials.jenkov.com/java-concurrency/read-write-locks.html