可重入读写锁原理分析

前段时间看了一系列并发编程博客,感觉写的不错。这里记录一下其中可重入读写锁的自己实现方法,虽然java中都有封装好的读写锁可用,但分析一下代码有助于理解锁机制的原理。
博客原地址


package cn.wcl.readWriteLock;

import java.util.HashMap;
import java.util.Map;

/**
 * @author www
 * 可重入读写锁demo
 * 【只是一种模拟,用于理解可重入读写锁的原理,java中有已经封装好的锁可以使用,无需自己设计锁】
 * 基于情况:写操作比读操作优先级高,读操作比写操作频繁得多。
 * 同一时间可以有多个线程对资源进行读取,但获取写锁的线程是唯一的
 * 获取读锁的条件:没有线程正在做写操作,且没有线程在请求写操作
 * 获取写锁的条件:没有线程正在做读写操作
 * @time:2017年4月26日 下午5:06:46
 */
public class ReadWriteLock {

    /**
     * 拥有读锁的Thread:用一个map来存储已经持有读锁的线程以及对应线程获取读锁的次数(用于可重入锁统计重入次数)
     */
    private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();
    /**
     * 写锁的个数
     */
    private int writeAccesses = 0;
    /**
     * 申请写锁的线程数
     */
    private int writeRequest = 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);
    }

    /**
     * @author www
     * 是否能获得读锁许可
     * @param callingThread
     * @return
     */
    private boolean canGrantReadAccess(Thread callingThread) {
        //注意条件的顺序,代表了它们的优先级
        if(isWriter(callingThread)) return true;//如果当前线程在写,那么该线程也可以读
        if(hasWriter()) return false;//有其他线程在写,那么当前线程不能读
        if(isReader(callingThread)) return true;//当前线程已经获得读锁,可以重入
        if(hasWriterRequests()) return false;//当前线程没有获得读锁,有其他线程请求写锁,则不能读,因为读优先级低
        //没有写锁请求,可能有其他线程在读,也能没有,当前线程可以获得读锁,因为多个线程可以一起读
        return true;
    }

    public synchronized void unlockRead() {
        Thread callingThread = Thread.currentThread();
        if(!isReader(callingThread)) {
            throw new IllegalMonitorStateException("callingThread未持有读锁!");
        }
        int readAccessCount = getReadAccessCount(callingThread);
        if(readAccessCount == 1) {
            readingThreads.remove(callingThread);
        } else {
            //如果重入过,重入次数-1
            readingThreads.put(callingThread, readAccessCount - 1);
        }
        //用notifyAll()!
        //因为读操作可以所有线程一起进行,只notify()唤醒一个,会导致只有一个等待的读线程获得锁,效率低
        notifyAll();
    }

    public synchronized void lockWrite() throws InterruptedException {
        writeRequest ++;
        Thread callingThread = Thread.currentThread();
        while(!canGrantWriteAccess(callingThread)) {
            wait();
        }
        writeAccesses ++;
        writeRequest --;
        writingThread = callingThread;
    }

    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 synchronized void unlockWrite() {
        Thread callingThread = Thread.currentThread();
        if(!isWriter(callingThread)) {
            throw new IllegalMonitorStateException("callingThread未持有写锁!");
        }
        writeAccesses --;
        /*writingThread = null;*/ //不能直接置为null,因为有可能是重入锁
        if(writeAccesses == 0) {
            writingThread = null;
        }
        notifyAll();
    }

    private boolean hasReaders() {
        return !readingThreads.isEmpty();
    }

    private boolean isOnlyReader(Thread callingThread) {
        return readingThreads.size() == 1 && readingThreads.containsKey(callingThread);
    }

    private boolean hasWriterRequests() {
        return writeRequest > 0;
    }

    private boolean isReader(Thread callingThread) {
        return readingThreads.containsKey(callingThread);
    }

    private boolean hasWriter() {
        return writingThread != null;
    }

    private boolean isWriter(Thread callingThread) {
        return writingThread == callingThread;
    }

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

猜你喜欢

转载自blog.csdn.net/blacktal/article/details/72832547