首页 综合 > 正文

JUC同步锁原理源码解析一 之ReentrantLock_全球观天下

2023-06-15 05:20:17 来源:博客园
JUC同步锁原理1.锁的本质1.什么是锁?

​通俗来讲,锁要保证的就是原子性,就是一个代码块不允许多线程同时执行,就是锁。从生活的角度上来说,就比如你要去上厕所,当你在上厕所期间,你会把门锁上,其他人只能排队。不允许多个人同时上厕所。

2.锁的底层实现

​java语言是运行在jvm之上,jvm是由C++实现的。java本身没有对应的底层锁实现,它将锁的问题抛给了C++。C++将锁的实现抛给了汇编语言,汇编语言将问题抛给操作系统。所以最后还是由操作系统提供的cmpxchg指令。通过 lock cmpxchg指令实现了cpu对于单个变量的原子操作。lock cmpxchg涉及到缓存一致性协议(MESI)与总线锁。(个人能力有限,自行了解吧)


(相关资料图)

3.什么是自旋锁

​线程不停执行某一个代码块,直到满足条件或者操作重试次数,也就是我们所说的CAS。

4.如何实现一把锁1.状态:判断当前是有锁还是无锁?

​boolean state = true/false;标识有锁与无锁。但是一旦有锁冲入的情况,我们就需要引入一个新的变量去存储锁冲入的次数。所以JUC中使用int state,默认值为0代表无锁状态。上增的数值代表所冲入的次数。

2.多线程如何抢锁

​通过cas实现多线程抢锁

3.抢不到锁的线程如何处理

​1.自旋,继续抢锁直到抢成功

​2.阻塞,线程阻塞直到有线程唤醒

​3.自旋+阻塞,自旋抢锁一定次数,如果失败,线程阻塞。

4.自旋锁的优缺点

​1.优点:节省线程上线文切换的时间。适用于执行步骤少且快的场景,节省cpu资源

​2.缺点:占用cpu资源,消耗cpu性能

​注意点:当cpu个数增加且线程数增加,可能导致自旋锁的优点退化成缺点。

2.AQS源码Node节点
static final class Node {        /** Marker to indicate a node is waiting in shared mode */        static final Node SHARED = new Node();        /** Marker to indicate a node is waiting in exclusive mode */        static final Node EXCLUSIVE = null;        /** waitStatus value to indicate thread has cancelled */        static final int CANCELLED =  1;        /** waitStatus value to indicate successor"s thread needs unparking */        static final int SIGNAL    = -1;        /** waitStatus value to indicate thread is waiting on condition */        static final int CONDITION = -2;         static final int PROPAGATE = -3;        volatile int waitStatus;        volatile Node prev;        volatile Node next;               volatile Thread thread;        Node nextWaiter;}
AbstractQueuedSynchronizer类
public abstract class AbstractQueuedSynchronizer    extends AbstractOwnableSynchronizer    implements java.io.Serializable {     private transient volatile Node head;    /**     * Tail of the wait queue, lazily initialized.  Modified only via     * method enq to add new wait node.     */    private transient volatile Node tail;    /**     * The synchronization state.     */    private volatile int state;//最重要的一个变量       }
ConditionObject类
public class ConditionObject implements Condition, java.io.Serializable {        private static final long serialVersionUID = 1173984872572414699L;        /** First node of condition queue. */        private transient Node firstWaiter;        /** Last node of condition queue. */        private transient Node lastWaiter;}
accquire方法
public final void acquire(int arg) {    if (!tryAcquire(arg) &&//尝试获取锁        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//如果获取锁失败,添加到队列中,由于ReentrantLock是独占锁所以节点必须是EXCLUSIVE类型        selfInterrupt();//添加中断标识位}
addWaiter方法
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);//cas失败后执行入队操作,继续尝试     return node; }private Node enq(final Node node) {    for (;;) {        Node t = tail;//获取尾指针        if (t == null) { //代表当前队列没有节点            if (compareAndSetHead(new Node()))//将当前节点置为头结点                tail = head;        } else {//当前队列有节点            node.prev = t;//            if (compareAndSetTail(t, node)) {//将当前节点置为尾结点                t.next = node;                return t;            }        }    }}
acquireQueued方法
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)) {//前驱节点等于头节点尝试cas抢锁。                setHead(node);//抢锁成功将当前节点设置为头节点                p.next = null; // help GC  当头结点置空                failed = false;                return interrupted;            }            if (shouldParkAfterFailedAcquire(p, node) &&//当队列中有节点在等待,判断是否应该阻塞                parkAndCheckInterrupt())//阻塞等待,检查中断标识位                interrupted = true;//将中断标识位置为true        }    } finally {        if (failed)//            cancelAcquire(node);//取消当前节点    }}private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {    int ws = pred.waitStatus;//获取上一个节点的等待状态    if (ws == Node.SIGNAL)//如果状态为SIGNAL,代表后续节点有节点可以唤醒,可以安心阻塞去        /*             * This node has already set status asking a release             * to signal it, so it can safely park.             */        return true;    if (ws > 0) {//如果当前状态大于0,代表节点为CANCELLED状态        /*             * Predecessor was cancelled. Skip over predecessors and             * indicate retry.             */        do {            node.prev = pred = pred.prev;//从尾节点开始遍历,找到下一个状态不是CANCELLED的节点。将取消节点断链移除        } while (pred.waitStatus > 0);        pred.next = node;    } else {        /*             * waitStatus must be 0 or PROPAGATE.  Indicate that we             * need a signal, but don"t park yet.  Caller will need to             * retry to make sure it cannot acquire before parking.             */        //这里需要注意ws>0时,已经找到了一个不是取消状态的前驱节点。        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//将找到的不是CANCELLED节点的前驱节点,将其等待状态置为SIGNAL    }    return false;} private void cancelAcquire(Node node) {     // Ignore if node doesn"t exist     if (node == null)//当前节点为空直接返回         return;     node.thread = null;//要取消了将当前节点的线程置为空     // Skip cancelled predecessors     Node pred = node.prev;//获取到当前节点的前驱节点     while (pred.waitStatus > 0)//如果当前节点的前驱节点的状态大于0,代表是取消状态,一直找到不是取消状态的节点         node.prev = pred = pred.prev;     Node predNext = pred.next;//将当前要取消的节点断链     node.waitStatus = Node.CANCELLED;//将当前节点的等待状态置为CANCELLED     // If we are the tail, remove ourselves.     if (node == tail && compareAndSetTail(node, pred)) {//如果当前节点是尾结点,将尾结点替换为浅语节点         compareAndSetNext(pred, predNext, null);//将当前节点的下一个节点置为空,因为当前节点是最后一个节点没有next指针     } else {         // If successor needs signal, try to set pred"s next-link         // so it will get one. Otherwise wake it up to propagate.         int ws;         if (pred != head &&//前驱节点不等于头结点             ((ws = pred.waitStatus) == Node.SIGNAL ||//前驱节点的状态不等于SIGNAL              (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&//前驱节点的状态小于0,并且cas将前驱节点的等待置为SIGNAL             pred.thread != null) {//前驱节点的线程补位空             Node next = node.next;//获取当前节点的next指针             if (next != null && next.waitStatus <= 0)//如果next指针不等于空并且等待状态小于等于0,标识节点有效                 compareAndSetNext(pred, predNext, next);//将前驱节点的next指针指向下一个有效节点         } else {             unparkSuccessor(node);//唤醒后续节点 条件:1.前驱节点是头结点 2.当前节点不是signal,在ReentransLock中基本不会出现,在读写锁时就会出现         }         node.next = node; // help GC 将引用指向自身     } } 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);//cas将当前节点置为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;//将s置为空         for (Node t = tail; t != null && t != node; t = t.prev)//从尾结点遍历找到一个不是取消状态的节点             if (t.waitStatus <= 0)                 s = t;     }     if (s != null)//如果s不等于空         LockSupport.unpark(s.thread);//唤醒当前节点s }

总结:AQS提供了统一的模板,对于如何入队出队以及线程的唤醒都由AQS提供默认的实现,只需要子类实现自己上锁和解锁的逻辑。

3.ReentrantLock基本使用
public class ReentrantLock extends Thread {private static ReentrantLock lock=new ReentrantLock(true); //参数为true表示为公平锁,请对比输出结果    public void run() {        for(int i=0; i<100; i++) {            lock.lock();            try{                System.out.println(Thread.currentThread().getName()+"获得锁");            }finally{                lock.unlock();            }        }    }    public static void main(String[] args) {        ReentrantLock rl=new ReentrantLock();        Thread th1=new Thread(rl);        Thread th2=new Thread(rl);        th1.start();        th2.start();    }}
lock方法:
public void lock() {    sync.lock();}

在AQS中对于具体的lock方法,并不做具体的实现,由子类自由拓展。ReentrantLock中lock方法中的实现分为公平锁和非公平锁的实现。所以lock方法有两个实现。

公平锁FairSync的实现
final void lock() {    acquire(1);//获取锁}public final void acquire(int arg) {    if (!tryAcquire(arg) &&//尝试获取锁        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//如果获取锁失败,添加到队列中,由于ReentrantLock是独占锁所以节点必须是EXCLUSIVE类型        selfInterrupt();//添加中断标识位}
tryAcquire方法
protected final boolean tryAcquire(int acquires) {    final Thread current = Thread.currentThread();//获取到当前线程    int c = getState();//获取当前的同步状态值    if (c == 0) {//代表没有线程占有锁        if (!hasQueuedPredecessors() &&//是否有前驱节点            compareAndSetState(0, acquires)) {//如果没有前驱节点,cas将当前状态值置为acquires,也就是1,成功代表获取到锁            setExclusiveOwnerThread(current);//标识当前属于互斥状态线程的拥有者是当前线程            return true;//true,代表获取锁成功        }    }    else if (current == getExclusiveOwnerThread()) {//进入这里代表state不为0,有其他线程获得锁        int nextc = c + acquires;//锁冲入,将冲入次数加1        if (nextc < 0)//冲入次数不能少于0,少于0是非法值,抛出异常            throw new Error("Maximum lock count exceeded");        setState(nextc);//设置state状态值        return true;//true,代表获取锁成功    }    return false;//返回false,代表获取锁失败} public final boolean hasQueuedPredecessors() {     // The correctness of this depends on head being initialized     // before tail and on head.next being accurate if the current     // thread is first in queue.     Node t = tail; // 获取队列的尾指针     Node h = head;// 获取队列的头指针     Node s;     return h != t &&//如果头结点和尾结点不是同一个节点         ((s = h.next) == null || s.thread != Thread.currentThread());//头结点的下一个节点为空或者当前头结点的线程不等于当前线程。 }
addWaiter方法
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);//cas失败后执行入队操作,继续尝试     return node; }private Node enq(final Node node) {    for (;;) {        Node t = tail;//获取尾指针        if (t == null) { //代表当前队列没有节点            if (compareAndSetHead(new Node()))//将当前节点置为头结点                tail = head;        } else {//当前队列有节点            node.prev = t;//            if (compareAndSetTail(t, node)) {//将当前节点置为尾结点                t.next = node;                return t;            }        }    }}
acquireQueued方法
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)) {//前驱节点等于头节点尝试cas抢锁。                setHead(node);//抢锁成功将当前节点设置为头节点                p.next = null; // help GC  当头结点置空                failed = false;                return interrupted;            }            if (shouldParkAfterFailedAcquire(p, node) &&//当队列中有节点在等待,判断是否应该阻塞                parkAndCheckInterrupt())//阻塞等待,检查中断标识位                interrupted = true;//将中断标识位置为true        }    } finally {        if (failed)//            cancelAcquire(node);//取消当前节点    }}private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {    int ws = pred.waitStatus;//获取上一个节点的等待状态    if (ws == Node.SIGNAL)//如果状态为SIGNAL,代表后续节点有节点可以唤醒,可以安心阻塞去        /*             * This node has already set status asking a release             * to signal it, so it can safely park.             */        return true;    if (ws > 0) {//如果当前状态大于0,代表节点为CANCELLED状态        /*             * Predecessor was cancelled. Skip over predecessors and             * indicate retry.             */        do {            node.prev = pred = pred.prev;//从尾节点开始遍历,找到下一个状态不是CANCELLED的节点。将取消节点断链移除        } while (pred.waitStatus > 0);        pred.next = node;    } else {        /*             * waitStatus must be 0 or PROPAGATE.  Indicate that we             * need a signal, but don"t park yet.  Caller will need to             * retry to make sure it cannot acquire before parking.             */        //这里需要注意ws>0时,已经找到了一个不是取消状态的前驱节点。        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//将找到的不是CANCELLED节点的前驱节点,将其等待状态置为SIGNAL    }    return false;} private void cancelAcquire(Node node) {     // Ignore if node doesn"t exist     if (node == null)//当前节点为空直接返回         return;     node.thread = null;//要取消了将当前节点的线程置为空     // Skip cancelled predecessors     Node pred = node.prev;//获取到当前节点的前驱节点     while (pred.waitStatus > 0)//如果当前节点的前驱节点的状态大于0,代表是取消状态,一直找到不是取消状态的节点         node.prev = pred = pred.prev;     Node predNext = pred.next;//将当前要取消的节点断链     node.waitStatus = Node.CANCELLED;//将当前节点的等待状态置为CANCELLED     // If we are the tail, remove ourselves.     if (node == tail && compareAndSetTail(node, pred)) {//如果当前节点是尾结点,将尾结点替换为浅语节点         compareAndSetNext(pred, predNext, null);//将当前节点的下一个节点置为空,因为当前节点是最后一个节点没有next指针     } else {         // If successor needs signal, try to set pred"s next-link         // so it will get one. Otherwise wake it up to propagate.         int ws;         if (pred != head &&//前驱节点不等于头结点             ((ws = pred.waitStatus) == Node.SIGNAL ||//前驱节点的状态不等于SIGNAL              (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&//前驱节点的状态小于0,并且cas将前驱节点的等待置为SIGNAL             pred.thread != null) {//前驱节点的线程补位空             Node next = node.next;//获取当前节点的next指针             if (next != null && next.waitStatus <= 0)//如果next指针不等于空并且等待状态小于等于0,标识节点有效                 compareAndSetNext(pred, predNext, next);//将前驱节点的next指针指向下一个有效节点         } else {             unparkSuccessor(node);//唤醒后续节点 条件:1.前驱节点是头结点 2.当前节点不是signal,在ReentransLock中基本不会出现,在读写锁时就会出现         }         node.next = node; // help GC 将引用指向自身     } } 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);//cas将当前节点置为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;//将s置为空         for (Node t = tail; t != null && t != node; t = t.prev)//从尾结点遍历找到一个不是取消状态的节点             if (t.waitStatus <= 0)                 s = t;     }     if (s != null)//如果s不等于空         LockSupport.unpark(s.thread);//唤醒当前节点s }
非公平锁FairSync的实现lock方法
final void lock() {    if (compareAndSetState(0, 1))//上来直接抢锁。        setExclusiveOwnerThread(Thread.currentThread());//抢锁成功,将当前互斥锁的拥有线程设置为当前线程    else        acquire(1);//cas失败去acquire}public final void acquire(int arg) {    if (!tryAcquire(arg) &&//尝试获取锁        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//如果获取锁失败,添加到队列中,由于ReentrantLock是独占锁所以节点必须是EXCLUSIVE类型        selfInterrupt();//添加中断标识位}protected final boolean tryAcquire(int acquires) {    return nonfairTryAcquire(acquires);}
nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {     final Thread current = Thread.currentThread();//获取到当前线程     int c = getState();//获取当前状态     if (c == 0) {//代表没有锁         if (compareAndSetState(0, acquires)) {//cas自旋尝试获得锁             setExclusiveOwnerThread(current);//当前独占锁的拥有线程设置为当前线程             return true;//返回         }     }     else if (current == getExclusiveOwnerThread()) {//如果当前线程之前已经获取到锁         int nextc = c + acquires;//锁重入         if (nextc < 0) // overflow 锁重入不能为0             throw new Error("Maximum lock count exceeded");         setState(nextc);//设置最新的state状态         return true;     }     return false; }

acquireQueued方法和addWaiter方法与公平锁的实现一致

锁释放
public void unlock() {    sync.release(1);} public final boolean release(int arg) {     if (tryRelease(arg)) {//尝试释放锁         Node h = head;         if (h != null && h.waitStatus != 0)//如果头结点不为空并且头结点的等待状态不等于0             unparkSuccessor(h);//唤醒头结点         return true;     }     return false; }protected final boolean tryRelease(int releases) {    int c = getState() - releases;//当前的状态减去释放锁的数量    if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程不是独占锁的线程,没锁还要释放,不就抛出异常了吗        throw new IllegalMonitorStateException();    boolean free = false;    if (c == 0) {//状态为0        free = true;        setExclusiveOwnerThread(null);//将当前互斥锁的拥有线程设置为空    }    setState(c);//设置状态位    return free;}
4.留言

本文章只是JUC 中AQS的一部分,后续的文章会对基于AQS锁实现的子类进行拓展讲解,以上文章内容基于个人以及结合别人文章的理解,如果有问题或者不当之处欢迎大家留言交流。由于为了保证观看流畅性,其中一部分源码有重复的地方。请见谅

上一篇 :世界热门:台式电脑怎么定时关机设置 台式电脑怎么定时关机 下一篇 :最后一页
x
推荐阅读