含泪总结Java读写锁

在某个风和日丽的一天,某人品鉴Java并发之美,看到读写锁不以为然,太阳过于耀眼,眼睛一花,不知不觉把秘诀看反,并且觉得无比正确。
时隔一月,某日月黑风高,某人再次拜读并发之美,发现自己大错特错,泪流满面,悔不当初。遂连夜含泪写下此篇文章。
如有谬误,还请指正。


ReentrantReadWriteLock作为一个开始吧。

import java.util.concurrent.locks.ReentrantReadWriteLock;

读写锁ReentrantReadWriteLock的家族:

ReadWriteLock是一个接口,内部由两个Lock接口组成

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

由ReentrantReadWriteLock实现了该接口。
如果想用读写锁,简单的来说大概是这样:

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

public class TestReadWriteLock {
    public static void main(String[] args){
        ReadWriteLock rwL = new ReentrantReadWriteLock();
        Lock read_lock = rwL.readLock();
        read_lock.lock();
        read_lock.unlock();
        Lock write_lock = rwL.writeLock();
        write_lock.lock();
        write_lock.unlock();
    }
}

读写锁的机制是这样的,读线程和读线程之间不用互斥写线程和写线程之间互斥读线程和写线程之间互斥

读写锁具体是怎么实现的呢?
看ReentrantReadWriteLock源码:
下面的是两个构造方法,默认是非公平的。也可以指定是否公平。

	public ReentrantReadWriteLock() {
        this(false);
    }

    /**
     * Creates a new {@code ReentrantReadWriteLock} with
     * the given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }


ReadLock和WriteLock的代码是一模一样的。
关键在于sync:

final Sync sync; 
//Sync是一个内部抽象类,继承了AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
		……
}

下面具体看Sync内部类的定义:

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 6317671515068378041L;

        /*
         * Read vs write count extraction constants and functions.
         * Lock state is logically divided into two unsigned shorts:
         * The lower one representing the exclusive (writer) lock hold count,
         * and the upper the shared (reader) hold count.
         */

        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 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; }
		
		……


大概意思就是把state变量拆两半,低16位记录写锁,高16位记录读锁
这里有三个点:

  • 写锁的16位表示什么
  • 读锁的16位表示什么
  • 为什么要把state变量拆两半来用

写锁的低16位表示一个写线程重入了多少次。
读锁的高16位表示有多少个线程拿到了读锁,也可以表示一个线程重入了多少次。
将一个int型变量拆成两半,是因为无法用一次CAS同时操作两个int变量。


ReadLock

public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;
        
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

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

		public void unlock() {
            sync.releaseShared(1);
        }

acquireShared和releaseShared是AQS里的模板方法
AQS中的源码:

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

WriteLock

public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;

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

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

		public void unlock() {
            sync.release(1);
        }

AQS中的源码:

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }


最终的对应关系:

  • 读锁的公平实现:Sync.tryAccquireShared() + FairSync
  • 读锁的非公平实现:Sync.tryAccquireShared() + NonfairSync
  • 写锁的公平实现:Sync.tryAccquire() + FairSync
  • 写锁的非公平实现:Sync.tryAccquire() + NonfairSync

ReentrantReadWriteLock的内部类FairSync

static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }


再看内部类NonfairSync:

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
    }


读锁获取步骤:

  • 若写锁被其他线程持有,返回-1
  • 根据公平非公平决定是否阻塞
  • CAS获取锁,更新统计变量

写锁获取流程

  • 若有线程获取了读锁,返回false
  • 若拿到写锁的线程不是当前线程,返回false
  • 若当前线程获取写锁的次数超过最大重入次数,抛出异常
  • 若无线程获取写锁,CAS抢锁

总结
Java中的读写锁机制是读读不互斥,读写互斥,写写互斥。
主要实现类是ReentrantReadWriteLock,该类内部有两个内部类分别为ReadLock和WriteLock,
两个内部类通过调用AQS的模板方法来实现lock和unlock,模板方法由内部类Sync实现,并且Sync有两个子类来实现公平和非公平机制。读写锁通过获取state,来控制锁的获取,并且再根据读的共享和写的独占细化具体抢锁操作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值