并发包(JUC)之Lock和AQS

1、Lock接口的实现——并发包锁

(1)ReentrantLock

  重入锁,重入锁指线程在获得锁之后,当该线程再次请求获得该锁不需要阻塞,而是可以直接获得锁,同时计数器增加重入次数。不同线程还是会阻塞的。

(2)ReentrantReadWriteLock

  重入读写锁,实现了ReadWriteLock接口,它维护两个锁,一个ReadLock,一个WriteLock,这两个都实现了Lock。基本原则是:读和读不互斥,读和写互斥,写和写互斥。适合读多写少的场景。

(3)StampedLock

  jdk1.8引入的新的锁机制,可以认为是对ReentrantReadWriteLock的改进版本。解决大量的读线程存在,可能会引起写线程的饥饿的问题。stampedLock是一种乐观的读策略,使得乐观锁完全不会阻塞写线程。

2、ReentrantLock的实现原理

  重入锁的设计目的:比如调用demo方法获得了当前的对象锁,然后在这个方法中再去调用 demo2,demo2中的存在同一个实例锁,这个时候当前线程会因为无法获得 demo2的对象锁而阻塞,就会产生死锁。重入锁的设计目的是避免线程的死锁。

  关系图:

        

(1)AQS 

  全称 AbstractQueuedSynchronizer,是实现Lock的核心组件。从功能层面分为两种:独占和共享。

    *  独占锁,ReentrantLock就是以独占方式实现的互斥锁。

    *  共享锁,如ReentrantReadWriteLock。

  AQS内部实现:AQS队列内部维护了一个FIFO的双向链表,双向链表可以从任意一个节点访问很方便地访问前驱和后继节点(非公平锁很好地利用了这一点特性),每个节点(Node)由线程和等待状态封装而成,当线程争抢锁失败,会被封装成Node添加到链表末端。

      

   Node节点

static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
        volatile Node prev; //前驱节点
        volatile Node next; //后继节点
        volatile Thread thread;//当前线程
        Node nextWaiter; //存储在condition队列中的后继节点
        //是否为共享锁
        final boolean isShared() { 
            return nextWaiter == SHARED;
        }

        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }
        //将线程构造成一个Node,添加到等待队列
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        //这个方法会在Condition队列使用,后续单独写一篇文章分析condition
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

(2)结合AQS看ReentrantLock实现原理  

  假设ThreadA、B、C三个线程同时访问一个同步块:

    

   如果ThreadA通过CAS获得了锁,此时state=1,exclusiveOwnerThread=ThreadA。ThreadB和ThreadC会被封装成Node节点,形成双向链表。

    

(3)公平锁和非公平锁(默认)

  FairSync在尝试获取锁时,多了一个hasQueuePredecessors判断,也就是如果当前线程(Node)有前置节点,则不会进行CAS去竞争锁,NonFairSync则不会做这个检查,直接通过CAS去竞争锁。

(4)ReentrantLock偏向锁的巧妙实现

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

  nonfairTryAcquire方法是lock方法间接调用的第一个方法,每次请求锁时都会首先调用该方法。该方法会首先判断当前状态,如果c==0说明没有线程正在竞争该锁,如果c !=0 说明有线程正拥有了该锁,但如果发现是自己拥有该锁的话,只是简单地++acquires,并修改status值,但因为没有竞争,所以通过setStatus修改,而非CAS,也就是说这段代码实现了偏向锁的功能。

(5)ReentrantLock和synchronized区别

  • ReentrantLock 会通过多次CAS尝试获取锁,这降低了线程上下文切换的概率,一定程度提高了性能;而 synchronized 一旦升级为重量级锁,只要申请锁失败就会就会进入队列中阻塞。

  • ReentrantLock 功能更多,比如对于读多写少的场景,可以使用读写锁,性能要高很多。

  • ReentrantLock 可以被中断,但是 synchronized 不能被中断。

  • ReentrantLock 可以通过 tryLock(timeout) 来设置线程等待时长。

  • ReentrantLock 有公平锁和非公平锁,synchronized 只有非公平锁。

 3、Synchronized与ReentrantLock实现原理有何不同?

  锁的实现原理基本是为了达到一个目的:让所有的线程都能看到某种标记。Synchronized通过在对象头中设置标记实现了这一目的,是一种JVM原生的锁实现方式,而ReentrantLock以及所有的基于Lock接口的实现类,都是通过用一个volitile修饰的int型变量,以此来保证每个线程都能拥有对该int变量的可见性和原子修改,其本质是基于AQS框架。

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    static final class Node {
  
        private volatile int state;
}

 推荐文章:

  分析ReentrantLock原理:https://mp.weixin.qq.com/s/3tqBo47GtG3ljdrig2b8AA

posted @ 2020-04-24 17:06  jingyi_up  阅读(91)  评论(0编辑  收藏  举报