并发包(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区别

  *  AbstractQueuedSynchronizer通过构造一个基于阻塞的CLH队列容纳所有的阻塞线程,而对该队列的操作均通过CAS操作,但对已经获得锁的线程而言,ReentrantLock实现了偏向锁的功能。

  *  synchronized的底层也是一个基于CAS操作的等待队列,但JVM实现的更精细,把等待队列分为ContentionList和EntryList,目的是为了降低线程的出列速度;当然也实现了偏向锁,从数据结构来说二者设计没有本质区别。但synchronized还实现了自旋锁,并针对不同的系统和硬件体系进行了优化,而Lock则完全依靠系统阻塞挂起等待线程。

  *  Lock比synchronized更适合在应用层扩展,可以继承AbstractQueuedSynchronizer定义各种实现,比如实现读写锁(ReadWriteLock),公平或不公平锁;同时,Lock对应的Condition也比wait/notify要方便的多、灵活的多。

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

 推荐文章:

1、深入剖析ReentrantLock:https://mp.weixin.qq.com/s/XMsFNCB0m7eTlH56ipZL7A

2、AQS源码分析:https://mp.weixin.qq.com/s/6Znq_SDZwzwifIIUSBRcMg

  

原文地址:https://www.cnblogs.com/jing-yi/p/12768678.html