JAVA多线程的初级认识5-ReentrantLock的初级认识

7 篇文章 0 订阅

锁除了Synchronized之外,DougLea大神还提供了Lock来帮忙进行资源的锁定。Lock接口作为JUC的核心组件,它比synchronized更加灵活,适用更多场景。下面就简单介绍下Lock的基本实现。

基本实现锁

ReentrantLock

可重入锁
当同步代码段再次进入用相同资源锁定的同步代码段时候,重入次数+1;

ReentrantLock的基本实现:

具体实现类图如下
在这里插入图片描述
ReentrantLock的实现基本是依赖AQS.

AQS

大致分为两类:共享和独占
在代码中AQS里面维护了一个双线链表,用于存储阻塞的线程。当线程竞争失败后,则会被包装秤链表中的一个节点(Node)维护起来。
Node 的源码如下:
在这里插入图片描述

AQS.Node

这个类是AQS双向列表里面的节点,保存了Thread以及一个waitStatus的状态位。
Thread信息不用多赘述,就是被block的Thread, 但是这个waitStatus表示什么,如下是java源码注释:

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

简要说明:

  • SINGAL(信号)

该状态意味着线程因为park在block的双向队列中,只要获得锁的线程释放锁,该节点就等待着被唤醒~

  • CANCELLED(取消)

在同步队列中等待的线程等待超时或被中断,需要从同步队列中取 消该 Node 的结点, 其结点的 waitStatus 为 CANCELLED,即结束状态,进入该状 态后的结点将不会再变化

  • CONDITION(条件)

与同步阻塞队列一样,还有一个同步等待队列~和wait/notify接口相关,只有被notify之后的Node节点才会被放到阻塞队列当中。如果wait on a condition就会进入这个队列。

  • PROPAGATE(传播)

共享模式用于notifyAll只有的一种传播模式,可以通过该节点去递归的将所有等待队列节点transfer到同步阻塞队列当中。

锁的获取

通过时序图来更加直观的了解下:
在这里插入图片描述
通过核心代码分解来说明流程:

  • 入口ReentrantLock.lock():
    sync是一个内部静态类,实现了AQS。
public void lock() {
        sync.lock();
    }
  • sync.lock()–>非公平锁的lock:
    首先通过CAS获取锁,成功则将state设置为1,失败则再去走accquire逻辑
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
  • CAS来争抢独享锁–>通过unSafe来实现。
    该操作依赖于UnSafe类,该类实在sun.misc包下,非java标准。但是很多基础类库依赖于该类。该类提供了很多低层次的操作,包括内存屏障,CAS, 线程同步和挂起, 分配内存等操作。
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
  • AQS.acquire(1):
    获取独占锁,如果失败,则通过AddWaiter将线程拆入到双向列表的尾部。
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • NofairSync.tryAccquire()
    同时如果成功,则累计重入次数。
        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;
        }
  • AQS.addWaiter():
    将当前线程封装成node,插入到双向链表的尾端。
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
  • AQS.acquireQueued()
    通过自旋的方式,当前节点去获取锁,获取成功则线程继续。如果获取失败,则将考虑将进程进行挂起或者check 是否被interrupt。
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • AQS.shouldParkAfterFailedAcquire & parkAndCheckInterrupt
    这两个函数就是为了判断节点是否需要被挂起~如果挂起,则用LockSupport.park挂起。

图解实例:
在这里插入图片描述
在这里插入图片描述

锁的释放

Lock.unLock()
由于所有的同步队列中的阻塞线程都被LockSupport.park了~unLock方法只是通过循环阻塞队列,将Node中需要被唤醒的Thread unPark即可。由于线程阻塞的时候是在自旋的循环中,所以被unPark的线程会立即tryAccquire操作。源码:

    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
          //值得注意的地方,这里是通过后往前来进行遍历的,是因为在插入的时候,可能node.next为null。具体看如下AQS.enq源码
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

主要在enq的node.prev = t 时候,t.next 还没赋值。但是该node可以被唤醒,所以从后往前~

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

ReentrantReadWriteLock

可重入读写锁
基本原理是读写锁分离,读锁共享,写锁独占。适用于读多写少的场景。

得到WriteLock或者ReadLock有两种方法:

  • 一种是通过ReentrantReadWriteLock来获取,当然这样就共享了内部的sync类进行同步
  • 一种是直接new,但是需要传入一个ReentrantReadWriteLock对象,这样也依赖于ReentrantReadWriteLock对象的sync。

所以也就是读写锁的同步机制主要来源于ReentrantReadWriteLock的sync类

获取锁

读写锁源码(均实现Lock接口)的区别:

  • 读锁

    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);
            }
    }
    
  • 写锁

        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);
            }
        }
    
  • Sync源码:

    • 获取锁
    abstract static class Sync extends AbstractQueuedSynchronizer {
      //写锁
      	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) {
              // 被共享锁锁住
              if (w == 0 || current != getExclusiveOwnerThread())
                return false;
              // 通过高低16位来判断独享还是共享
              if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
              // 设置新的重入状态
              setState(c + acquires);
              return true;
            }
          //c=0,说明当前没有线程持有锁。竞争锁,同时判断是否为公平锁
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
              return false;
          // 竞争成功设置独享锁
            setExclusiveOwnerThread(current);
            return true;
        }
      //读锁
      	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();
          	//获取重入的独享状态
          	// 如果是独享锁,并且当前线程非独享线程,false
                if (exclusiveCount(c) != 0 &&
                    getExclusiveOwnerThread() != current)
                    return -1;
          	// 获取重入共享状态
                int r = sharedCount(c);
          	// 公平锁和非公平锁的should block同时共享状态小于最大共享线程数量(1<<16 - 1)
          	// 然后进行cas
                if (!readerShouldBlock() &&
                    r < MAX_COUNT &&
                    compareAndSetState(c, c + SHARED_UNIT)) {
                  // 如果没有线程持有读锁,则持有并设置firstReader为当前线程和firstReaderHoldCount为1
                    if (r == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                  // 类似重入,增加firstReaderHoldCount数量
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                  // 已经有线程持有读锁,并且非当前线程
                    } else {
                        HoldCounter rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                          	// cacheHoldCounter用来保存最近的一次线程持有读锁和对应数量
                            cachedHoldCounter = rh = readHolds.get();
                        else if (rh.count == 0)
                          	// readHolds里面维护的是threadlocal变量来持有各个线程的tid并且记录持有的数量
                            readHolds.set(rh);
                      	// 对应线程持有的数量+1
                        rh.count++;
                    }
                    return 1;
                }
          		// 这部分代码就不解释了,具体comment是说这部分代码冗余...看流程将基本也是在自旋然后获取共享锁以及更新cache
                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;
                    }
                }
            }
    }
    
总结
  • state状态被分为两种计算方式,高16位和低16位,低16位计算独享锁,高16位计算共享锁
  • 写锁 和独享锁类似,只是过滤了一下重入的次数,流程如下:
    • 判断是否已经被读锁持有或者超过了独享锁的重入最大值
    • 如果没有锁,则按照公平或者非公平锁进行CAS以及设置独享锁
  • 读锁 类似于共享锁,重入的次数变量没有用到,流程如下:
    • 判断是否已经被写锁持有
    • 判断读锁是否超过阈值同时CAS
    • 然后对读锁的信息进行更新
  • 读锁 内部维护了4个变量
    • firstReader 第一次获取读锁的线程
    • firstReaderHoldCount 第一次读锁线程持有的读锁重入数量
    • cachedHoldCounter 用来记录最近线程的次数和线程信息
    • readHolds ThreadLocal变量,用来记录每个线程的信息和各自持有读锁的个数

释放锁

读锁和写锁释放的源码以及注释:

       //独享锁
				protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
          	// 判断读锁是否都已经被释放掉了
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
              	// 释放读锁
                setExclusiveOwnerThread(null);
           //这里不用CAS是因为只有独享锁的线程才能访问这段代码
            setState(nextc);
            return free;
        }

        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;
            }
        }
总结
  • 释放锁和获取锁相反,减少或者清空对应的数量或者标志位
  • 写锁流程:
    • 判断是否是独享线程
    • 减少对应的写锁的state(低16位)
    • 写锁state =0 清空独享线程
    • 更新state
  • 读锁流程:
    • 清空firstReader,firstReaderHoldCount。更新当前线程读锁线程的持有读锁的状态(cachedHoldCounter,readHolds)
    • 通过CAS设置state

总结

  • 读锁和写锁均是来自于ReentranReadWriteLock
  • 共享了Sync类和state
  • 所以读锁和写锁之间有相互的干扰,虽然在A代码设置了读锁,B代码设置了写锁,两个是不同的lock,但是同样会相互block~
  • 还有学会了三个大于号**>>>**是表示无符号数的右移

用例demo

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

public class ReentrantReadWriteLockDemo {

    static ReentrantReadWriteLock reentrantReadWriteLockDemo = new ReentrantReadWriteLock();

    public static void main(String[] args) throws InterruptedException {
        ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLockDemo.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLockDemo.writeLock();

        new Thread(new WriteLockClass(writeLock)).start();
        Thread.sleep(1000);
        new Thread(new ReadLockClass(readLock)).start();
    }

    static class ReadLockClass implements Runnable{

        Lock lock;

        ReadLockClass(Lock lock){
            this.lock = lock;
        }

        @Override
        public void run() {
            try{
                lock.lock();
                System.out.println(this.lock.getClass().getSimpleName() + "\t is working!!!");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    static class WriteLockClass implements Runnable{

        Lock lock;

        WriteLockClass(Lock lock){
            this.lock = lock;
        }

        @Override
        public void run() {
            try{
                lock.lock();
                System.out.println(this.lock.getClass().getSimpleName() + "\t is working!!!");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

StampedLock

JDK 1.8引入的新锁机制,他并没有实现任何锁的接口。可以简单认为是读写 锁的一个改进版本,读写锁虽然通过分离读和写的功能使得读和读之间可以完全 并发,但是读和写是有冲突的,如果大量的读线程存在,可能会引起写线程的饥饿。 stampedLock 是一种乐观的读策略,使得乐观锁完全不会阻塞写线程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值