Lock和synchronized同步块一样,是线程的同步机制,而且锁是由synchronized同步块的方式进行实现的。
一个简单的锁实现
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();
}
}
while(isLocked)循环,也称作旋转锁。旋转锁可以防止虚假唤醒。
锁的可重入性
Java中的synchronized同步块是可以重入的。也就是说线程可以进入同一管程对象锁同步的另一个java代码块。
上面的锁实现是不可以重入的,因为上面的实现中一个线程是否允许退出lock方法是有循环决定的,只有当isLocked为false的时候,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改进中,记录了已锁住该Lock实例的线程。如果当前锁对象没有被加锁或者当前调用线程已经对该Lock实例加了锁,那么就不会执行wait方法。
另外还需要记录同一线程重复对一个锁对象加锁的次数,否者调用一次unlock就会解除该对象的整个锁。在unlock调用没有到达时lock调用次数之前,我们不希望锁被解除。
在finally语句中调用unlock()
使用Lock加锁来保护临界区,并且临界区可能会抛出异常,那么久必须在finally语句中调用unlock()。这样可以保证该锁对象可以被解锁以便其他线程
能对其继续加锁。
读写锁
多线程read 没有write ==> 线程安全
一个或者多个线程write ==> 线程不安全
- Read Access
没有线程在写数据&&没有线程请求写数据 - Write Access
没有其他线程在读或者写
读写锁的简单实现:
- 如果没有线程进行写请求或者写操作的时候,可以获取读操作权限
- 如果没有线程进行有读锁且没有线程有写锁的时候,可以获取写操作的权限
- 使用notifyAll唤醒线程,可以防止信号丢失
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();
}
}
实现可重入的读写锁
可重入读写锁的实现:
- 读锁重入
读锁可重入,要么满足获取读锁的条件(没有写或者写请求), 要么已经持有读锁 - 写锁重入
一个线程已经拥有写锁,该线程允许重入
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;
}
}
重入锁死
当一个线程重新获取锁,该锁是不可重入的,则可能导致重入锁死。
public class Reentrant{
public synchronized outer(){
inner();
}
public synchronized inner(){
//do something
}
}
避免重入锁死的方法:
- 编写代码时避免再次获取已经持有的锁
- 使用重入锁