AQS应用-ReentrantReadWriteLock(可重入读写锁)

生活

今天礼拜六,晴,加班,真好!

_____

今天来研究一下可重入读写锁-ReentrantReadWriteLock。
首先要明确的一点是,前面研究的可重入锁ReentrantLock,其实是一个互斥锁,保证在同一时刻只有一个线程获取资源,无论是写写,读读,读写任何场景都是互斥的。这种互斥锁在常见的高并发场景下会显现出糟糕的低吞吐量问题。
事实上,在高并发的实际场景下,读的频率远高于写,而且读本身不会对数据进行任何修改,只是读取,因此希望对读的操作进行共享,而对写操作进行独占。
这就是读写锁的作用。
它的特性就是:一个资源可以被多个读操作同时访问,或者被一个写操作独占。但是两者不能同时访问。

源码

初识ReentrantReadWriteLock

ReentrantReadWriteLock并没有继承自ReentrantLock 也 并不实现Lock接口,而是实现独有的ReadWriteLock接口。

此接口下仅有两个方法
 //获取读锁
  Lock readLock();

//获取写锁
  Lock writeLock();

如果创建一个读写锁,简单看下构造器

//默认创建非公平读写锁
    public ReentrantReadWriteLock() {
        this(false);
    }


//也可以通过指定fair 为true创建一个公平的读写锁
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
//至于这里的Sync 继承自AQS,FairSync和NonFairSync都继承自Sync,其实现与可重入锁中实现差不多。不赘述了、

从ReentrantLock的具体实现中得知,其独占性和重入性都是通过对AQS内state变量的操作实现的。
ReentrantReadWriteLock中由于需要实现读和写两个操作,把state这个int变量分为高16位和低16位。
高16位为读锁占用量,低16位为写锁占用量。

读锁分析

读锁lock()的方法下,关键方法如下


 protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();

          // 如果是独占锁(写锁)并且并不是当前线程独占,直接返回-1,获取读锁失败
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);


           // 三个判断
           //1、读锁是否需要被阻塞:非公平锁不需要阻塞、公平锁下,如果在等待队列中存在其他线程,则返回true,
           //否则返回false,往下走
           //2、获取读锁的数量小于最大数量
           //3、CAS操作将读锁占有量+1
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
//第一次获取读锁
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                    //可重入获取读锁,对应记录+1
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
 
                   //rh是记录最后一个
                    HoldCounter rh = cachedHoldCounter;
                    //如果并不是,就从ThreadLoccal取并设置为最后一个
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
//这里的代码同上基本差不多,不赘述
            return fullTryAcquireShared(current);
        }


当获取读锁失败,就进入AQS等待队列,在判断前方节点为SIGNAL状态后进入等待状态,等待被唤醒后竞争资源。
     

读锁unLock()方法

一目了然 没啥好说的。。
 protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // Releasing the read lock has no effect on readers,
                    // but it may allow waiting writers to proceed if
                    // both read and write locks are now free.
                    return nextc == 0;
            }
        }

写锁分析

写锁lock()方法:

 protected final boolean tryAcquire(int acquires) {
        
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
             
             // 是独占锁且不说当前线程直接返回false
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            //是否需要阻塞,公平锁下,当等待队列中有其他正在等待的线程,需要阻塞
            
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

写锁unlock方法

很简单,不说了
protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

案例

public class RRWLTest {
	
	public static void main(String[] args) {
		ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
		ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
		 ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
		CountDownLatch latch = new CountDownLatch(1);
		new Thread(new Test(readLock, null, 0, "A", latch)).start();
		new Thread(new Test(null, writeLock, 2000, "B", latch)).start();
		new Thread(new Test(readLock, null, 0, "C", latch)).start();
		new Thread(new Test(null, writeLock, -500, "D", latch)).start();
		new Thread(new Test(readLock, null, 0, "E", latch)).start();
		new Thread(new Test(readLock, null, 0, "F", latch)).start();
		new Thread(new Test(null, writeLock, 600, "G", latch)).start();
		try {
			Thread.sleep(1000L);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		latch.countDown();
		

		
	}
	
	static class Test implements Runnable{
		
		public static volatile int account =0;
		private ReentrantReadWriteLock.ReadLock readLock;
		private ReentrantReadWriteLock.WriteLock writeLock;
		private int money;
		private String name;
		private CountDownLatch latch;
		


		@Override
		public void run() {
			try {
				latch.await();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if(null!=readLock) {
				readLock.lock();
				System.out.println(String.format("name:%s,查询余额:%s", name,account));
				readLock.unlock();
			}
			else {
				writeLock.lock();
				account+=money;
				if(money>0) {
					System.out.println(String.format("name:%s,存款:%s,余额:%s", name,money,account));
	
				}else {
					System.out.println(String.format("name:%s,取款:%s,余额:%s", name,-money,account));

				}
				writeLock.unlock();
			}
			
		}



		public Test(ReadLock readLock, WriteLock writeLock, int money, String name, CountDownLatch latch) {
			super();
			this.readLock = readLock;
			this.writeLock = writeLock;
			this.money = money;
			this.name = name;
			this.latch = latch;
		}



		
		
		
		
	}

}

运行结果:
name:B,存款:2000,余额:2000
name:D,取款:500,余额:1500
name:G,存款:600,余额:2100
name:C,查询余额:2100
name:F,查询余额:2100
name:A,查询余额:2100
name:E,查询余额:2100

后记

明天礼拜天,可以睡懒觉了

猜你喜欢

转载自blog.csdn.net/qq_28605513/article/details/84454711