读写锁ReentrantReadWriteLock详解

1. 读写锁简介

  • 读写锁的内部包含两把锁:一把是读(操作)锁,是一种共享锁;另一把是写(操作)锁,是一种独占锁
  • 在没有写锁的时候,读锁可以被多个线程同时持有
  • 写锁被一个线程持有,其他的线程不能再持有写锁,抢占写锁会阻塞,抢占读锁也会阻塞

读写互斥原则:

  1. 读读相容
  2. 读写互斥
  3. 写写互斥

解决线程安全 问 题使用 ReentrantLock 就可以 ,但是 ReentrantLock 是独占锁 ,某时只有一个线程可以获取该锁,而实际中会有写少读多的场景,因此就需要读写锁ReentrantReadWriteLock

JUC包中的读写锁接口为ReadWriteLock:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();//返回读锁

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();//返回写锁
}

2. ReentrantReadWriteLock类图分析

在这里插入图片描述

1. ReentrantReadWriteLock实现了ReadWritrLock接口

public interface ReadWriteLock {
  
    Lock readLock();
    Lock writeLock();
}

2. FairSync、NonfairSync继承Sync类,提供了公平和非公平的实现

static final class NonfairSync extends Sync{
}
static final class FairSync extends Sync {
}
abstract static class Sync extends AbstractQueuedSynchronizer{
 }

3. WriteLock、Sync、ReadLock、FairSync、NonfaruSyns都是ReadWritrLock的静态内部类

AQS 中只维护了 一个 state 状态,而 ReentrantReadWriteLock 则 需要维护读状态和写状态

public abstract class AbstractQueuedSynchronizer{
	 private volatile int state;//state是int类型 32位
}

用 state 的高16 位表示读状态,也就是获取到读锁的次数;使用低 16 位表示获取到写锁的线程的可重入次数 。

 	    static final int SHARED_SHIFT   = 16;
 	    
 	    //共享锁(读锁)状态单位值 65536  1<<16  2^16
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        
        //共享锁线程最大个数 65535
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        
		//排它锁(写锁)掩码,二进制,15 个 1
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

		//返回读锁线程数
         /** Returns the number of shared holds represented in count  */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

		//返回写锁可重入个数
        /** Returns the number of exclusive holds represented in count  */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

		//firstReader 用来记录第一个获取到读锁的线程
 		private transient Thread firstReader = null;
 		//firstReaderHoldCount 则记录第 一个获取到读锁的线程获取读锁的可重入次数 
        private transient int firstReaderHoldCount;
        
	//cachedHoldCounter 用来记录最后 一个获取读锁的线程获取读锁 的可重入次数 
		private transient HoldCounter cachedHoldCounter;
/*
readHolds是ThreadLocal变量 ,用来存放除去第一个获取读锁线程外的其他线程获取读锁的可重入次数 。ThreadLocaHoldCounter 继承了ThreadLocal ,因而重写的initialValue 方法返回 一个 HoldCounter 对象 
*/
private transient ThreadLocalHoldCounter readHolds;
static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }
        
static final class HoldCounter {
            int count = 0;
            // Use id, not reference, to avoid garbage retention
            final long tid = getThreadId(Thread.currentThread());
        }

3. 写锁的获取和释放

ReentrantReadWriteLock 中 写锁使用 WriteLock 来实现

  • 写锁是可重入锁
  • 如 果当前没有线程获取到读锁和写锁, 则当前线程可以获取到写锁然后返回
  • 如果当前己经有线程获取到读锁和写锁,则当前请求写锁的线程会被阻塞挂起

1. void lock()

public static class WriteLock implements Lock, java.io.Serializable {
	 public void lock() {
            sync.acquire(1);
        }
}
//AbstractQueuedSynchronizer类
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
//Sync类
protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            //c!=0说明读锁或者写锁已经被某线程获取
            if (c != 0) {//代码1处
                // (Note: if c != 0 and w == 0 then shared count != 0)
                // w=O说明已经有线程获取了读锁
                // w!=O 并且当前线程不是写锁拥有者则返回false
                if (w == 0 || current != getExclusiveOwnerThread())//代码2处
                    return false;
                //说明当前线程获取了写锁,判断可重入次数
                if (w + exclusiveCount(acquires) > MAX_COUNT)//代码3处
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);//设置可重入次数  代码4处
                return true;
            }
            //第 一个写线程获取写锁  
            //代码5处
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

tryAcquire方法中:

代码1处:如果当前 AQS 状态值不为 0 则说明 当前己经有线程获取到了读锁或者写锁
代码2处:

  1. 如果 w==0 说明状态值 的低16 位为0(低16位表示写状态) ,而 AQS 状态值不为 0,则说明高16位(高16位表示读状态)不为 0 ,这暗示己经有线程获取了读锁 ,所以直接返回 false
  2. 如果 w! =0 则说明 当前已经有线程获取了该写锁,再看当前线程是不是该锁的持有者 ,如果不是则返回 false

代码3处:说明当前线程之前已经获取到了该锁,所以判断该线程的可重入次数是不是超过了最大值,是则抛出异常

代码4处: 增加当前线程的可重入次数,返回true

代码5处: AQS 的状态值等于 0则说明目前没有线程获取到读锁和写锁,让第 一个写线程获取写锁

//NonfairSync类
// writerShouldBlock方法发非公平锁实现
 final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
 if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))

代码对于非公平锁来说总是返回false, 因此会进行CAS尝试获取写锁,获取成功则设置当前锁的持有者为当 前线程并返回 true ,否则返回 false

//FairSync类
// writerShouldBlock方法发公平锁实现
 final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }

使用 hasQueuedPredecessors来判断当前线程节点是否有前驱节点,如果有则当前线程放弃获取写锁的权限 ,直接返回 false, 公平在于先来先服务,有前驱节点说明当前线程前面还有其他先来的线程在排队

2. void lockInterruptibly()

 public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(1);
        }

类似于lock()方法,它的不同 之处在于 ,它会对中断进行响应 ,也就是当其他线程调用了该线程的interrupt方法中断了 当 前线程时 , 当 前线程会抛出 异常 InterruptedExcep tion异常。

3. boolean tryLock( )

尝试获取写锁 非公平实现

//WriteLock类
 public boolean tryLock( ) {
            return sync.tryWriteLock();
        }
//Sync类
final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c != 0) {
                int w = exclusiveCount(c);
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
            }
            if (!compareAndSetState(c, c + 1))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }
  • 如果当前没有其他线程持有写锁或者读锁,则当前线程获取写锁会成功 ,然后返回true 。
  • 如果当前己经有其他线程持有写锁或者读锁则该方法直接返回 false,且当前线程并不会被阻塞
  • 如果当前线程已经持有了该写锁则 简单增加 AQS 的状态值后直接返回true 。

4. void unlock()

尝试释放锁

//WriteLock类
 public void unlock() {
            sync.release(1);
        }
//AQS类
 public final boolean release(int arg) {
 //调用ReentrantReadWriteLock中sync类实现的tryRelease方法
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)//激活阻塞队列里面的一个线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
//Sync类
protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())//看是否是写锁拥有者调用的unlock
                throw new IllegalMonitorStateException();
            //获取可重入值,这里没有考虑高16位,因为获取写锁时读锁状态值肯定为0
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)//如采写锁可重入值为0则释放锁,否则只是简单地更新状态值
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }
  • 如果当前线程持有该锁,调用该方法会让该线程对该线程持有的 AQS状态值减1
  • 如果减去1后当前状态值为0则当前线程会释放该锁 ,否则仅仅减1而己
  • 如果当前线程没有持有该锁而调用了该方法则 会抛出 Illega!MonitorStateException 异常

4. 读锁的获取和释放

ReentrantReadWri teLock 中的读锁是使用 ReadLock 来实现的

1. void lock()

//ReadLock类
 public void lock() {
            sync.acquireShared(1);
        }
//AQS类
 public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

//java.util.concurrent.locks.ReentrantReadWriteLock.Sync.tryAcquireShared(int)
protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();//获取当前状态值  代码1
					//代码2处
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)//判断是否写锁被占用
                return -1;
            int r = sharedCount(c);//获取读锁计数  代码3处
            //尝试获取锁 ,多个读线程只有一个会成功,不成功的进入fullTryAcquireShared进行重试
            if (!readerShouldBlock() && //代码4处
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {//第 一个线程获取读锁  代码5处
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {//如果当前线程是第一个获取读锁的线程  代码6处
                    firstReaderHoldCount++;
                } else {
                //记录最后一个获取读锁的线程或记录其他线程读锁的可重入数
                    HoldCounter rh = cachedHoldCounter; //代码7处
                    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);//代码8处
        }

tryAcquireShared方法解释:
1. 代码1处首先获取了当前 AQS 的状态值
2. 代码2处查看是否有其他线程获取到了写锁,如果是则直接返回-1
3. 代码3处,得到获取到的读锁的个数 , 到这里说明目前没有线程获取到写锁 ,但是可能有线程持有读锁
4. 代码4处的中非公平锁的 readerShouldBlock 实现代码如下:

//java.util.concurrent.locks.ReentrantReadWriteLock.NonfairSync.readerShouldBlock()
final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
//java.util.concurrent.locks.AbstractQueuedSynchronizer.apparentlyFirstQueuedIsExclusive()
 final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }

如果队列里面存在一个元素,则判断第一个元素是不是正在尝试获取写锁,如果不是(!readerShouldBlock()=true),则当前线程判断当前获取读锁的线程是否达到了最大值( r < MAX_COUNT)。 最后执行CAS 操作将 AQS 状态值的高 16 位值增1(compareAndSetState(c, c + SHARED_UNIT)) 。

5. 代码5处和代码6处记录第一个获取读锁的线程并统计该线程获取读锁的可重入数
6. 代码7处使用 cachedHoldCounter 记录最后一个获取到读锁的线程和该线程获取读锁的可重入数
readHolds记录了当前线程获取读锁的可重入数
7. 如果可以到代码8处,说明readerShouldBlock()=true,说明有线程正在获取写锁,fullTryAcquireShared的代码与tryAcquireShared 类似(尝试获取共享锁),它们的不同之处在于,前者通过循环自旋获取

final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                } else if (readerShouldBlock()) {
                    // Make sure we're not acquiring read lock reentrantly
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

2. void lockInterruptibly()

//java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock.lockInterruptibly()
 public void lockInterruptibly() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }

类似于 lock方法,不同之处在于,该方法会对中断进行响应,也就是当其他线程调用了该线程的interrupt方法中断了当前线程时,当前线程会抛出异常

3. boolean tryLock()

尝试获取读锁

 public boolean tryLock() {
            return sync.tryReadLock();
        }
  • 如果当前没有其他线程持有写锁,则当前线程获取读锁会成功,然后返回 true
  • 如果当前己经有其他线程持有 写锁则该方法直接返回 false,但当前线程并不会被阻塞
  • 如果当前线程己经持有了该读锁则简单增加 AQS 的状态值高 16 位后直接返回 true

4. void unlock()

//ReadLock类
   public void unlock() {
            sync.releaseShared(1);
        }
//java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared(int)
 public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
//java.util.concurrent.locks.ReentrantReadWriteLock.Sync.tryReleaseShared(int)
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;
            }
        }
  • 在无限循环里面,首先获取当前 AQS 状态值并将其保存到变量c,然后变 量 c 被减去一个读计数单位后使 用 CAS 操作 更新 AQS 状态值
  • 如果更新成功则查看当前 AQS 状态值是否为 0(nextc == 0) ,为 0 则说明当前己经没有读线程占用读锁,则tryReleaseShared 返回 true ,然后会调用 doReleaseShared 方法释放一个由于获取写锁而被阻塞的线程
  • 如果当前 AQS 状态值不为 0,则说明 当前还有其他线程持有了 读锁 ,所以trγReleaseShared 返回 false
  • 如果tryReleaseShared 中的 CAS 更新 AQS 状态值失败,则自旋重试直到成功。

4. 使用案例

直接使用ArrayList是线程不安全的:

package innerlock;

import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockDemo {
	private static ArrayList<String> array=new ArrayList<>();//线程不安全的list
	private final ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
	private final Lock readLock=lock.readLock();
	private final Lock writeLock=lock.writeLock();
	public void add(String e) {
		writeLock.lock();
		try {
			array.add(e);
		} finally {
			writeLock.unlock();
		}
	}
	public void remove(String e) {
		writeLock.lock();
		try {
			array.remove(e);
		} finally {
			writeLock.unlock();
		}
	}
	public String get(int index) {
		readLock.lock();
		try {
			return array.get(index);
		} finally {
			readLock.unlock();
		}
	}
	public static void main(String[] args) throws InterruptedException {
		for(int i=0;i<2;i++) {
			new Thread(()->{
				for(int j=0;j<5;j++)
					array.add("str"+j);
			}).start();
		}
		Thread.sleep(1000);
		System.out.println(array);
	}
}`package innerlock;

import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockDemo {
	private ArrayList<String> array=new ArrayList<>();//线程不安全的list
	private final ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
	private final Lock readLock=lock.readLock();
	private final Lock writeLock=lock.writeLock();
	public void add(String e) {
		writeLock.lock();//写锁
		try {
			array.add(e);
		} finally {
			writeLock.unlock();
		}
	}
	public void remove(String e) {
		writeLock.lock();
		try {
			array.remove(e);
		} finally {
			writeLock.unlock();
		}
	}
	public String get(int index) {
		readLock.lock();//读锁
		try {
			return array.get(index);
		} finally {
			readLock.unlock();
		}
	}
	public static void main(String[] args) throws InterruptedException {
		ReentrantReadWriteLockDemo obj=new ReentrantReadWriteLockDemo();
		for(int i=0;i<2;i++) {
			new Thread(()->{
				for(int j=0;j<5;j++)
					obj.add("str"+j);
			}).start();
		}
		Thread.sleep(1000);
		System.out.println(obj.array);
	}
}
`

在这里插入图片描述

安全版本:

package innerlock;

import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockDemo {
	private ArrayList<String> array=new ArrayList<>();//线程不安全的list
	private final ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
	private final Lock readLock=lock.readLock();
	private final Lock writeLock=lock.writeLock();
	public void add(String e) {
		writeLock.lock();//写锁
		try {
			array.add(e);
		} finally {
			writeLock.unlock();
		}
	}
	public void remove(String e) {
		writeLock.lock();//写锁
		try {
			array.remove(e);
		} finally {
			writeLock.unlock();
		}
	}
	public String get(int index) {
		readLock.lock();//读锁
		try {
			return array.get(index);
		} finally {
			readLock.unlock();
		}
	}
	public static void main(String[] args) throws InterruptedException {
		ReentrantReadWriteLockDemo obj=new ReentrantReadWriteLockDemo();
		for(int i=0;i<2;i++) {
			new Thread(()->{
				for(int j=0;j<5;j++)
					obj.add("str"+j);
			}).start();
		}
		Thread.sleep(1000);
		System.out.println(obj.array);
	}
}

在这里插入图片描述

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodePanda@GPF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值