juc-locks

作者: 我是大兄弟 | 来源:发表于2019-07-08 00:07 被阅读0次

    juc-locks 锁框架

    java提供各类锁的一个框架

    锁的性质划分

    多个线程是否按顺序获取锁的角度划分--公平锁与非公平锁

    \color{red}{公平锁}:当锁被某个线程持有时,新发出请求的线程会被放入队列中,在公平锁中,每一次尝试获取锁都会检查CLH队列中是否仍有前驱的元素,如果仍然有那么继续等待

    \color{red}{非公平锁}:非公平锁和公平锁在获取锁的方法上,流程是一样的;它们的区别主要表现在“尝试获取锁的机制不同”,无视等待队列直接尝试获取锁,如果锁是空闲的,即可获取状态,则获取锁

    \color{red}{优缺点}:公平锁为了保证线程排队,需要增加阻塞和唤醒的时间开销,非公平锁是可以减少唤起线程的开销,缺点是处于等待队列中的线程可能会出现一直获取不到锁的现象(饿死),如果线程处理时间过长,一部分客户端获取不到服务(违反公平原则,一直被新的线程占用cpu),此时可以使用公平锁,否则使用非公平锁提供吞吐量

    \color{red}{饥饿原因}
    高优先级线程占用所有CPU,低优先级线程一致无法获取
    一个线程每次竞争锁都失败, 而且新的线程还在一直不断竞争,从而导致这个线程就几乎是一直处于等待中
    某个线程在某个对象的条件队列上等待,而其他线程不断的抢入

    多个线程是否可以持有同一把锁的角度划分--独享锁与共享锁

    \color{red}{独享锁(互斥锁/写锁)}:是指该锁一次只能被一个线程所持有

    \color{red}{共享锁(读锁)}:是指该锁可被多个线程所持有

    一个线程能否重复获取自己的锁的角度划分--重入锁(递归锁)与不重入锁

    \color{red}{重入锁}:同一个线程而言,它可以重复的获取锁,避免同一个线程重复获取锁发生死锁

    \color{red}{不重入锁}:一个线程多次请求同一把锁,会出现死锁

    \color{red}{优缺点}:重入锁和不可重入锁主要的差别在对相同线程是否能够重复获取,从效率来说,不可重入锁效率更高

    锁的设计方案来分类

    多个线程竞争同一把锁是否进行阻塞划分--自旋锁与自适应自旋锁

    \color{red}{自旋锁(spinlock)}:指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程给阻塞,直到那个获得锁的线程释放锁之后,这个线程就可以马上获得锁的

    \color{red}{自适应自旋锁}:自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定,如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源

    \color{red}{优缺点}:自旋锁是一种非阻塞锁,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取自旋锁,自旋锁不会使线程状态发生切换执行速度快,但是会不断消耗CPU

    多个线程并发时锁的粒度以及锁竞争划分--分段锁

    \color{red}{分段锁}:在锁发生竞争使用独享锁保护受限资源时,一次只能有一个线程能访问独享锁,可以采取一种机制来协调独享锁来提高并发性,这个机制就是分段锁,这项技术使得ConcurrentHashMap(JDK1.7)支持多达16个并发的写入操作

    \color{red}{优缺点}:与单个锁来相比,获取多个锁来实现独占访问将更加困难并且开销更高

    多个线程写操作干扰本身线程的程度划分--乐观锁/悲观锁

    \color{red}{乐观锁}:认为每次去读数据时,不会进行其他写操作,采用CSA
    \color{red}{悲观锁}:认为每次去读数据时,都有写操作,类似synchronized

    \color{red}{优缺点}:悲观锁是指当一个线程获取锁时其他线程只能进行阻塞,并且进程挂起和恢复执行过程中也存在着很大的开销,CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试,使用乐观锁如果存在大量的写操作,需要不断重试,浪费CPU资源

    多个线程竞争程度划分--偏向锁/轻量锁/重量锁

    \color{red}{偏向锁}:只有一个申请锁的线程使用锁
    \color{red}{轻量锁}:多个线程交替使用锁,允许短时间的锁竞争
    \color{red}{重量锁}:有实际竞争,且锁竞争时间长

    \color{red}{优缺点}:如果一个同步方法,没有多线程竞争,并且总是由同一个线程多次获取锁,如果每次还有阻塞线程,那么对于CPU是一种资源的浪费,为了解决这类问题,就诞生了偏向锁(偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令,因为只有一个线程在竞争,我们只要去判断该偏向锁中的ThreadID是否为当前线程即可),轻量级锁是通过CAS来避免进入开销较大的互斥操作,CAS是无锁的可以避免线程状态切换带来的开销,但是不适合锁竞争时间长(线程计算慢),非常浪费CPU资源,重量级锁更适合时间长的情况下使用,可以避免CPU浪费

    Lock

    为了解决synchronized阻塞带来的性能问题,JDK1.5提供了线程同步工具Lock接口方便实现各类锁,从Lock提供的各类锁的角度来说,对比synchronized这种悲观锁要显得更加丰富和灵活

    synchronized不具备的特性 描述
    尝试锁 判断线程状态是否获取了锁,获得返回true否则false
    中断锁 线程获得锁继续执行,锁不可用则阻塞,阻塞情况下其他线程获得锁则抛出异常
    超时锁 线程超时未获取锁,返回false否则true
    释放锁 需要手动释放锁,不然造成死锁
    Lock API 描述
    lock() 获得锁,否则一直等待
    unlock() 释放锁
    tryLock() 判断锁状态,锁被占用就返回false,否则返回true
    tryLock(long time, TimeUnit unit) 比起tryLock()添加一个时间期限判断
    lockInterruptibly() 此种获取锁的方式,锁被占用的情况下抛出异常,直接中断,可以去做其他处理
    Condition newCondition() 通知组件:具有阻塞与唤醒功能

    Condition

    Object类中的wait()、notify()、notifyAll()方法实现了线程之间的通信,而Condition类中的await()、signal()、signalAll()方法也实现了相似的功能。通过Condition能够精细的控制多线程的休眠与唤醒, 对于一个锁,我们可以为多个线程间建立不同的Condition。ConditionObject是同步器AbstractQueuedSynchronizer的内部类,每个Condition对象都包含着一个队列,该队列是Condition对象实现等待/通知功能的关键

    \color{red}{同步队列}:AQS的同步排队用了一个隐式的双向队列,同步队列的每个节点是一个AbstractQueuedSynchronizer.Node实例

    \color{red}{等待队列}:Condition的等待队列是一个隐式的单向队列,等待队列中的每一个节点也是一个AbstractQueuedSynchronizer.Node实例

    \color{red}{总结}
    1:整个过程是节点在同步队列和等待队列中来回移动实现的
    2:每当一个线程调用Condition.await()方法,那么该线程会释放锁,构造成一个Node节点加入到等待队列的队尾,自旋判断如果当前节点没有在同步队列上则将当前线程阻塞
    3:调用Condition的signal()方法,会将节点移到同步队列中,但不一定在队首
    4:如果退出自旋说明当前节点已经在同步队列上。通过acquireQueued将阻塞直到当前节点成为队首,即当前线程获得了锁。然后await()方法就可以退出了,让线程继续执行await()后的代码

    \color{red}{参考资料}
    https://www.cnblogs.com/sheeva/p/6484224.html

    AbstractQueuedSynchronizer

    AQS提供一个框架,一个FOFO的等待队列和一个代表状态的int值,子类需要定义这个状态的protected方法,定义什么状态获取到状态以及释放锁状态,该类方法提供所有入队和阻塞机制,AQS框架提供了一套通用的机制来管理同步状态、阻塞/唤醒线程、管理等待队列,是JUC上大多数同步器的基础

    \color{red}{模板模式}:定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤

    \color{red}{模板方法}:AQS使用了一组模板方法,直接调用\color{red}{同步组件方法}

    AbstractQueuedSynchronizer的模板方法 描述
    void acquire(int arg) 会调用tryAcquire方法,如果未获取成功,则会进入同步队列,通过死循环,直到node节点的线程获取到锁,才返回
    void acquireInterruptibly(int arg) 会调用tryAcquire方法,如果未获取成功,则会进入同步队列,通过死循环,直到node节点的线程获取到锁,才返回,当外界对当前线程进行中断的时候提前结束获取状态的操作
    boolean tryAcquireNanos(int arg,long nanos) acquireInterruptibly方法的升级版,也就是在判断是否被中断的基础上增加了超时控制
    void acquireShared(int arg) 调用tryAcquireShared方法尝试获取共享锁,如果返回值大于0表示获取共享锁成功直接返回,否则入队阻塞,一直循环直到tryAcquireShared方法获取共享锁成功,如果共享状态获取成功之后会判断后继节点的状态是否Node.SIGNAL,如果是共享模式,那么就直接对其进行唤醒操作,也就是同时激发多个线程并发的运行
    void acquireSharedInterruptibly(int arg) acquireShared方法的基础上添加了中断判断,当外界对当前线程进行中断的时候提前结束获取状态的操作
    boolean tryAcquireSharedNanos(int arg,long nanos) acquireSharedInterruptibly方法的升级版,也就是在判断是否被中断的基础上增加了超时控制
    boolean release(int arg) 调用tryRelease方法释放当前持有的锁资源返回true,则判断队列头节点的状态不是0或者不是null则唤醒一个线程
    boolean releaseShared(int arg) 调用tryReleaseShared释放锁如果返回true,则唤醒后续节点
    Collection getQueuedThreads() 获取同步队列上的线程集合

    \color{red}{同步组件}:程序员需要选择覆盖实现以下方法来实现同步状态的管理

    自定义的同步组件 描述
    boolean isHeldExclusively() 判断线程状态是否获取了锁
    boolean tryAcquire(int arg) 独占式获取同步状态
    boolean tryRelease(int arg) 独占式释放同步状态
    int tryAcquireShared(int arg) 共享式获取同步状态
    boolean tryReleaseShared(int arg) 共享式私房同步状态

    \color{red}{同步状态}:同步状态的管理配合同步组件使用

    同步状态 描述
    int getState() 获取同步状态
    void setState() 设置同步状态,必须保证线程是安全的
    boolean compareAndSetState(int expect, int update) CAS 设置同步状态

    \color{red}{CLH同步队列}:CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,在CLH同步队列中,一个节点表示一个线程

    \color{red}{总结}:线程会首先尝试获取锁,失败则将当前线程以及等待状态等信息包成一个Node节点加到CLH队列来管理锁,接着通过死循环尝试获取锁,在拿到锁为止会不断阻塞,等到线程释放锁时,会唤醒队列中的后继线程

    \color{red}{参考资料}https://www.jianshu.com/p/0da2939391cf

    ReentrantLock

    ReentrantLock 描述
    ReentrantLock(boolean fair) 创建一个(公平/非公平)的具有可重入性质的实例对象

    \color{red}{Sync}:Sync为ReentrantLock的成员变量,Sync的子类分别为FairSync和NonfairSync

    非公平锁
    static final class NonfairSync extends Sync {
            private static final long serialVersionUID = 8424253563782823691L;
    
            /**
             * Performs lock.  Try immediate barge, backing up to normal
             * acquire on failure.
             */
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
    
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }
        
    final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
    

    1:通过CSA加锁,如果超成功则setExclusiveOwnerThread(设置独占锁),如果不成功代表以及持有锁,是一个重入的线程
    2:acquire为AbstractQueuedSynchronizer的模板方法,获取线程状态并调用compareAndSetState进行设置(加锁),加锁失败代表锁已经被占用,
    则判断当前线程是否锁的持有者,如果是则代表是一把重入锁。通过setState不断累加锁重入的次数

    释放锁
    protected final boolean tryRelease(int releases) {
                int c = getState() - releases;
                if (Thread.currentThread() != getExclusiveOwnerThread())
                    throw new IllegalMonitorStateException();
                boolean free = false;
                if (c == 0) {
                    free = true;
                    setExclusiveOwnerThread(null);
                }
                setState(c);
                return free;
            }
    

    如果当前线程不是锁的持有者,抛出异常,锁记录为0代表释放全部,返回true,否则设置同步状态返回false

    公平锁
    static final class FairSync extends Sync {
            private static final long serialVersionUID = -782823691563782823376L;
    
            final void lock() {
                acquire(1);
            }
    
            protected final boolean tryAcquire(int acquires) {
               final Thread current = Thread.currentThread();
               int c = getState();
               if (c == 0) {
                   if (!hasQueuedPredecessors() &&
                       compareAndSetState(0, acquires)) {
                       setExclusiveOwnerThread(current);
                       return true;
                   }
               }
               else if (current == getExclusiveOwnerThread()) {
                   int nextc = c + acquires;
                   if (nextc < 0)
                       throw new Error("Maximum lock count exceeded");
                   setState(nextc);
                   return true;
               }
               return false;
            }
        }
    

    调用hasQueuedPredecessors判断:当前线程不是同步队列有其他线程优先则返回false不能获取锁

    ReentrantReadWriteLock

    ReentrantReadWriteLock特性 描述
    公平与非公平 多个线程是否按顺序获取锁的角度
    重入 同一个线程它可以重复的获取锁
    锁降级 同一个线程中没有释放写锁的情况下,就去申请读锁属于锁降级
    public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
        private static final long serialVersionUID = -6992448646407690164L;
        /** Inner class providing readlock */
        private final ReentrantReadWriteLock.ReadLock readerLock;
        /** Inner class providing writelock */
        private final ReentrantReadWriteLock.WriteLock writerLock;
        /** Performs all synchronization mechanics */
        final Sync sync;
    }
    
    public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
        private static final long serialVersionUID = -6992448646407690164L;
        /** Inner class providing readlock */
        private final ReentrantReadWriteLock.ReadLock readerLock;
        /** Inner class providing writelock */
        private final ReentrantReadWriteLock.WriteLock writerLock;
        /** Performs all synchronization mechanics */
        final Sync sync;
    }
    
     /**
         * Synchronization implementation for ReentrantReadWriteLock.
         * Subclassed into fair and nonfair versions.
         */
        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; }
    }
    
    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();
    }
    

    读写锁是依赖AQS框架实现的共享锁与排它锁,AQS 维护一个state是32位的,内部类Sync采用一套运算规则,实现了高位保存共享锁,低位保存独占锁的一套逻辑

    public static class ReadLock implements Lock, java.io.Serializable {
            private static final long serialVersionUID = -5992448646407690164L;
            private final Sync sync;
    
            /**
             * Constructor for use by subclasses
             *
             * @param lock the outer lock object
             * @throws NullPointerException if the lock is null
             */
            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);
            }
            
            public void unlock() {
                sync.releaseShared(1);
            }
    
    }
    
    public final void acquireShared(int arg) {
            if (tryAcquireShared(arg) < 0)
                doAcquireShared(arg);
        }
        
    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();
                //exclusiveCount(c) --计数高低位 
                //exclusiveCount(c) != 0 --代表写锁
                //getExclusiveOwnerThread() != current -- 当前线程不是锁的持有者
                if (exclusiveCount(c) != 0 &&
                    getExclusiveOwnerThread() != current)
                    return -1;
                int r = sharedCount(c);
              // 如果readerShouldBlock()返回false,且获取次数r小于MAX_COUNT,且设置state状态成功
                if (!readerShouldBlock() &&
                    r < MAX_COUNT &&
                    compareAndSetState(c, c + SHARED_UNIT)) {
                    //r==0表示第一个线程
                    if (r == 0) {
                        firstReader = current;   //将线程设置为 第一个获取读锁的线程
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {   
                     // 如果线程等于 第一个获取读锁的线程 则代表重入 ,进行累加
                        firstReaderHoldCount++;
                    } else {          //代表有其他线程竞争,利用HoldCounter累加
                        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); //尝试获取读锁
            }
            
    
     final int fullTryAcquireShared(Thread current) {   
                /*
                 * This code is in part redundant with that in
                 * tryAcquireShared but is simpler overall by not
                 * complicating tryAcquireShared with interactions between
                 * retries and lazily reading hold counts.
                 */
                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;
                    }
                }
            }
    
    /**
         * Fair version of Sync
         */
        static final class FairSync extends Sync {
            private static final long serialVersionUID = -2274990926593161451L;
            final boolean writerShouldBlock() {
                return hasQueuedPredecessors();
            }
            final boolean readerShouldBlock() {
                return hasQueuedPredecessors();
            }
        }
        /**
         * Nonfair version of Sync
         */
        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();
            }
        }
    

    apparentlyFirstQueuedIsExclusive:只有当CLH队列第一个节点处于独占锁模式才返回true
    hasQueuedPredecessors返回true,表示在CLH队列中还有其他线程在当前线程前面

    static final class HoldCounter {
                int count = 0;
                // Use id, not reference, to avoid garbage retention
                final long tid = getThreadId(Thread.currentThread());
            }
    
            /**
             * ThreadLocal subclass. Easiest to explicitly define for sake
             * of deserialization mechanics.
             */
            static final class ThreadLocalHoldCounter
                extends ThreadLocal<HoldCounter> {
                public HoldCounter initialValue() {
                    return new HoldCounter();
                }
            }
    
            /**
             * The number of reentrant read locks held by current thread.
             * Initialized only in constructor and readObject.
             * Removed whenever a thread's read hold count drops to 0.
             */
            private transient ThreadLocalHoldCounter readHolds;
    
            /**
             * The hold count of the last thread to successfully acquire
             * readLock. This saves ThreadLocal lookup in the common case
             * where the next thread to release is the last one to
             * acquire. This is non-volatile since it is just used
             * as a heuristic, and would be great for threads to cache.
             *
             * <p>Can outlive the Thread for which it is caching the read
             * hold count, but avoids garbage retention by not retaining a
             * reference to the Thread.
             *
             * <p>Accessed via a benign data race; relies on the memory
             * model's final field and out-of-thin-air guarantees.
             */
            private transient HoldCounter cachedHoldCounter;
    
            /**
             * firstReader is the first thread to have acquired the read lock.
             * firstReaderHoldCount is firstReader's hold count.
             *
             * <p>More precisely, firstReader is the unique thread that last
             * changed the shared count from 0 to 1, and has not released the
             * read lock since then; null if there is no such thread.
             *
             * <p>Cannot cause garbage retention unless the thread terminated
             * without relinquishing its read locks, since tryReleaseShared
             * sets it to null.
             *
             * <p>Accessed via a benign data race; relies on the memory
             * model's out-of-thin-air guarantees for references.
             *
             * <p>This allows tracking of read holds for uncontended read
             * locks to be very cheap.
             */
            private transient Thread firstReader = null;
            private transient int firstReaderHoldCount;
    
            Sync() {
                readHolds = new ThreadLocalHoldCounter();
                setState(getState()); // ensures visibility of readHolds
            }
    

    计算读锁次数利用ThreadLocal为每一个线程维护一份独立的变量

    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;
                }
            }
    

    读锁的获取与释放过程中都用到了HoldCounter类,HoldCounter类主要起着计数器的作用,对读锁的获取与释放操作会更新对应的计数值。若线程获取读锁,则该计数器+1,释放读锁,该计数器-1

    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)
                    //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;
            }
    
           /*
             * Note that tryRelease and tryAcquire can be called by
             * Conditions. So it is possible that their arguments contain
             * both read and write holds that are all released during a
             * condition wait and re-established in tryAcquire.
             */
    
            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;
            }
    

    1:c != 0表示已经获取锁,判断当前线程是否锁的持有者是否获取写锁,是否小于写次数,都满足条件进行累加,否则进行阻塞尝试获取
    2:计算state值,不断进行累减,直到==0,代表释放锁成功

    //JDK提供的锁降级的一个例子
    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();
          }
        }
    

    LockSupport

    Object类的wait/notify机制相比,park/unpark有两个优点:1. 以thread为操作对象更符合阻塞线程的直观定义;2. 操作更精准,可以准确地唤醒某一个线程

    每个线程都有一个许可(permit),permit只有两个值1和0,默认是0,通过park/unpark设置

    StampedLock

    为什么有了ReentrantReadWriteLock,还在JDK1.8时引入StampedLock?

    读操作一直都能抢占到CPU时间片,而写操作一直抢不了,可能导致写的饥饿问题,正因为ReentrantReadWriteLock出现了读和写是互斥的情况,这个地方需要优化,因此就出现了StampedLock

    /**
         * Returns a stamp that can later be validated, or zero
         * if exclusively locked.
         *
         * @return a stamp, or zero if exclusively locked
         */
        public long tryOptimisticRead() {
            long s;
            return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
        }
    
        /**
         * Returns true if the lock has not been exclusively acquired
         * since issuance of the given stamp. Always returns false if the
         * stamp is zero. Always returns true if the stamp represents a
         * currently held lock. Invoking this method with a value not
         * obtained from {@link #tryOptimisticRead} or a locking method
         * for this lock has no defined effect or result.
         *
         * @param stamp a stamp
         * @return {@code true} if the lock has not been exclusively acquired
         * since issuance of the given stamp; else false
         */
        public boolean validate(long stamp) {
            U.loadFence();
            return (stamp & SBITS) == (state & SBITS);
        }
    

    tryOptimisticRead():当前没有线程持有写锁,则简单的返回一个非 0 的 stamp 版本信息
    validate():检测乐观读版本号是否变化

    最后

    本人做的一些学习总结,期间也参考了很多大师兄的资料,目前还有一些地方理解不到位,如果某些地方描述不够或者错误,还请各位大兄弟给予不同意见

    相关文章

      网友评论

        本文标题:juc-locks

        本文链接:https://www.haomeiwen.com/subject/fjuwhctx.html