Java并发系列之三 ReadWriteLock源码解析

1. ReadWriteLock接口

ReadWriteLock是一个java接口,它并没有继承Lock接口。提供了readLock()和writeLock(),分别返回一个读锁和写锁。

public interface ReadWriteLock {

    Lock readLock();

    Lock writeLock();
}

2. ReentrantReadWriteLock使用

ReentrantReadWriteLock类内部定义了静态内部类ReadLock和WriteLock。同时持有 readLock和writerLock对象。

public class ReadWriterLockUsage {
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private Map<String, String> map = new HashMap<>();
    public String get(String key) {
        try {
            reentrantReadWriteLock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " readLock");
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return map.get(key);
        } finally {
            reentrantReadWriteLock.readLock().unlock();
            System.out.println(Thread.currentThread().getName() + " readUnlock");
        }
    }

    public void put(String key, String value) {
        try {
            reentrantReadWriteLock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " writeLock");
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
            System.out.println(Thread.currentThread().getName() + " writeUnLock");
        }
    }

    public static void main(String[] args) {
        ReadWriterLockUsage usage = new ReadWriterLockUsage();
        for (int i = 0; i < 2; i++)
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    while (true)
                        usage.put("", "");
                }
            }.start();

        for (int i = 0; i < 2; i++)
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    while (true)
                        usage.get("");
                }
            }.start();
    }

}

运行结果可能如下

Thread-0 writeLock
Thread-0 writeUnLock
Thread-0 writeLock
Thread-0 writeUnLock
Thread-1 writeLock
Thread-1 writeUnLock
Thread-2 readLock
Thread-3 readLock
Thread-2 readUnlock
Thread-3 readUnlock
Thread-0 writeLock
Thread-0 writeUnLock
Thread-0 writeLock
Thread-0 writeUnLock
Thread-1 writeLock
Thread-1 writeUnLock
Thread-1 writeLock
Thread-1 writeUnLock

通过观察结果我们可以发现writeLock同一时刻只能被一个线程获得。而writeLock同一时刻可以被多个线程同时获得。

3. 源码解析

1. 锁状态

通过ReentrantLock的源码解析我们了解到,锁的状态由 private volatile int state来控制。每当线程获取到了锁 state会加1,每当线程释放了锁state会减1。当state=0表示锁当前处于非上锁状态。state定义在AbstractQueuedSynchronizer中。ReentrantLock持有一个AbstractQueuedSynchronizer对象。

那ReentrantReadWriteLock内部的锁是怎么实现的呢?我们知道ReentrantReadWriteLock内部有一个ReadLock和WriteLock。那么他们之间是完全独立的锁吗?如果不是独立的锁,那么state怎么来标识读写锁加锁的状态和次数呢?

事实上ReadLock和WriteLock是共用同一个AQS对象。AQS的state的值可以标识锁的状态。在读写锁的AQS实现中,int类型的state,我们知道int类型在计算机中有32位。它在读写所中,高16位标识读锁状态。低16位表示写锁状态。

    static final int SHARED_SHIFT   = 16;
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//值为FFFF
    //当前读锁(共享锁)被获取的数量 c >>> SHARED_SHIFT表示向右移动16个单位,结果是int的高16位
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    //写锁(独占锁)被获取的数量,低16位
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

2.WriteLock源码分析

首先我们来分析写锁的源码,因为写锁相对读锁更简单一些,写锁和ReentrantLock都是独占锁,所以我们先来分析它。还是从lock()开始分析

public static class  WriteLock implements Lock, java.io.Serializable {

    private final Sync sync;

    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        sync.acquire(1);
    }
}

我们知道sync的acquire(1)是个模板方法,先调用tryAcquire(1)方法如果获取成功,直接返回,如果获取失败,把线程封装成节点,加入队列,并且通过自旋来获取锁。在ReentrantLock源码分析中有详细讲解,读者如果不熟悉可以,先看ReentrantLock源码分析文章。我们重点来看下tryAcquire(1)在ReentrantReadWriteLock中的实现

 protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             *(如果读锁被获取 或者 写锁被获取而且获取到的线程不是当前线程,获取写锁失败)
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             *(如果锁的数量超过限制,获取失败)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             *(否则,如果该线程是可重入获取或队列策略允许,则该线程有资格进行锁定。 如果是这样,更新状态并设置所有者。)
             */
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                //如果 c != 0 and w == 0 表示读锁被线程占有了,获取锁失败
                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;
        }

如果当前读锁被获取了,再去获取写锁会失败。简单吧,如果失败了,走的还是ReentrantLock那一套

3. ReadLock源码解析

我们知道读锁是共享锁,同一时刻可以被多个线程同时获取。接下来我们来分析下。还是从lock()方法开始

 public static class ReadLock implements Lock, java.io.Serializable {

        private final Sync sync;


        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        /**
         * Acquires the read lock.
         *
         * <p>Acquires the read lock if the write lock is not held by
         * another thread and returns immediately.
         *
         * <p>If the write lock is held by another thread then
         * the current thread becomes disabled for thread scheduling
         * purposes and lies dormant until the read lock has been acquired.
         */
        public void lock() {
            //如果写锁没有被其他线程占有立马获取到读锁
            //如果写锁被其他线程占有,那么当前线程会阻塞直到读锁被获取到
            sync.acquireShared(1);
        }
}

我们来看下sync.acquireShared(1)方法

 public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)//如果尝试获取共享锁失败
            doAcquireShared(arg);
    }

我们先来看下doAcquireShared()吧,如果是独占锁尝试获取失败,之后的流程我们是比较清楚的,我们可以对比观察他们之间的区别

 private void doAcquireShared(int arg) {
        //加入到AQS队列中 和独占锁是一样的
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {//通过无限循环实现自旋功能和独占锁也是一样的
                final Node p = node.predecessor();
                if (p == head) {//如果是队首节点
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {//如果获取成功
                    //获取成功共享锁和独占锁是有区别的,独占锁获取成功直接return。
                    //共享锁如果获取成功它会告诉下一个等待获取共享锁线程去获取共享锁
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

我们再来看下tryAcquireShared()的实现吧

protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    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);
        }

关于锁降级

锁降级是指在获取写锁的同时,读锁也能获取到,当然这种情况只限于同一个线程。降级是对写锁而言,对于读锁个人觉得应该是锁升级。附上java源码的例子

class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        try {
          // Recheck state because another thread might have
          // acquired write lock and changed state before we did.
          if (!cacheValid) {
            data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock();
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read
        }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }

最下面那个读锁其实被升级为独占锁了,同一时刻只能被一个线程获取

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值