JAVA并发编程--4.1理解ReentrantLock

前言:
Lock 本质上是一个接口,它定义了释放锁和获得锁的抽象方法,定义成接口就意味着它定义了锁的一个标准规范,也同时意味着锁的不同实现。实现 Lock 接口的类有很多其中ReentrantLock:表示重入锁,它是唯一一个实现了 Lock 接口的类。重入锁指的是线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联一次计数器增加重入次数。

1 锁的设计:

  • 锁对于共享资源的互斥性,要有一种状态来标识锁的状态;
  • 没有获取到锁的线程需要有地方进行存放,线程能够被挂起(释放CPU资源)和唤醒;
  • 需要体现锁的公平性和非公平性;
  • 需要实现锁的重入性;
    技术实现:
  • 可以定义state 来标识锁的状态;
  • 可以使用wait/notify|condition ,LockSupport.park(),unpark() 支持挂起和唤醒线程;使用双向链表来存放没有获取到锁的线程;
  • 公平和非公平可以使用业务逻辑实现;
  • 锁的冲入性,可以判断当前的线程id 来判断是否是同一个线程获取到锁;

2 ReentrantLock 的实现:
2.1 示例代码:

package org.lgx.bluegrass.bluegrasscoree.util.lockUtil;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description TODO
 * @Date 2022/10/31 16:57
 * @Author lgx
 * @Version 1.0
 */
public class MyLock1  implements Runnable {
    static Lock lock = new ReentrantLock();
    private static int num = 0;
    @Override
    public void run() {
        lock.lock();// 获取锁
        try {
            Thread.sleep(1);
            num ++ ;
        } catch (InterruptedException e) {
// TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            lock.unlock();// 释放锁
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyLock1  lock1 = new MyLock1();
        for (int i = 0; i < 1000; i++) {
            new Thread(lock1).start();
        }
        Thread.sleep(2000);
        System.out.println(num);
    }

}

2.2 Lok.lock() 获取锁详解:
在这里插入图片描述
其中 ReentrantLock 对于获取锁有两种实现,公平锁: FairSync ,非公平锁:NonfairSync,通过new ReentrantLock() 传入参数控制:

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
    	// 默认非公平锁
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
      // true 时为公平锁
        sync = fair ? new FairSync() : new NonfairSync();
    }

2.2.1 先来看非公平锁(NonfairSync):
当一个线程首先通过 lock() 方法来获取非公平锁:

 /**
         * 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);
        }
        
       // compareAndSetState:替换state 的值如果此时内存中的state 为0则替换为1
         protected final boolean compareAndSetState(int expect, int update) {
	        // See below for intrinsics setup to support this
	        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
       }
       // setExclusiveOwnerThread:设置私有线程属性:
       /**
     * Sets the thread that currently owns exclusive access.
     * A {@code null} argument indicates that no thread owns access.
     * This method does not otherwise impose any synchronization or
     * {@code volatile} field accesses.
     * @param thread the owner thread
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
  • 其中: if (compareAndSetState(0, 1)) 通过cas 操纵尝试将标志位 从 0 替换为1 ,如果成功代表获取锁成功;
  • 如果获取到锁,setExclusiveOwnerThread(Thread.currentThread());通过set 操作将 将独占锁标识给与当前线程;
    线程获取锁成功即可执行后续业务;
  • 如果线程1已经获取锁并且没有释放,则线程2就会获取锁失败,执行 acquire(1) 将当前线程加入阻塞队列;

acquire(1) :

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

acquire 方法中 首先 使用 tryAcquire 方法再次获取一次锁(如果此时其它线程释放了锁,则当前线程可以获得锁)

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();// 获取当前线程
            int c = getState();// 获取state
            if (c == 0) { // state=0 说明没有线程去抢占锁
                if (compareAndSetState(0, acquires)) {// 通过cas 将0 置为1 标识获得锁
                    setExclusiveOwnerThread(current);// 设置当前线程为独占锁标识
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {// 重入锁当前线程已经获取到锁
                int nextc = c + acquires;// 将获得锁的次数加1
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);// 设置加锁的次数
                return true;
            }
            return false;
        }

如果获取锁失败则通过acquireQueued(addWaiter(Node.EXCLUSIVE), arg))将该线程2同步到阻塞队列中:
addWaiter(Node.EXCLUSIVE), arg) :此方法会将当前线程封装成一个node对象并 放入一个双向链表的队列中

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode); // 封装node 节点 此时waitStatus 为初始值0
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;// 将链表的尾节点指向  pred;( 初始时 tail 为null) 
        if (pred != null) {// 第一次pred 为null 不进入 if 
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);// 执行enq 来添加node 到 链表中
        return node;
    }

首先通过new Node 来获得一个node,Node node = new Node(Thread.currentThread(), mode);此时 volatile int waitStatus; // 默认值 0; 进入 enq(node) 方法:

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

该方法中传入为获取到锁封装线程2的 node 节点 进入for 循环:

     // 第一次for 循环:
         Node t = tail; // 此时 t 指向链表的尾节点 为null
            if (t == null) { // Must initialize  进行链表的初始化
                if (compareAndSetHead(new Node())) // 通过构造方法 生成一个空的node,并将head 指向头节点改node 节点
                    tail = head;  // 将链表的为节点指向 刚生成的node 节点
            }

第一次for 循环执行完成后,进行了一个双向链表的初始,此时链表结构如下图:
在这里插入图片描述

然后进行第二次for 循环:

for (;;) {
            Node t = tail; // 此时t(尾节点) 指向node  不为null
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {// 进入else
                node.prev = t; // 此时把 封装号 线程2的 node 节点的前置节点 指向 t
                if (compareAndSetTail(t, node)) {// 把链表的尾节点指向封装好的线程2 node节点  
                   t.next = node; // 把头节点的下一节点指向 封装好的线程2 node节点
                    return t; // 返回头节点
                }
            }
        }

进行第二次循环后此时链表结构如下图:
在这里插入图片描述

接下来:addWaiter(Node mode) 方法 返回封装好后的 线程2 node 节点;
扩展:此时如果线程3 进入lock.lock() 代码块中,获取锁失败也进入addWaiter方法时:

 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);// 封装线程3 node 节点
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail; // pred 为指向链表的最后一个节点,不为null
        if (pred != null) { // 此时进入if
            node.prev = pred;// 此时把封装好的线程3 的node 节点的上一节点指向原链表的尾节点(尾插法)
            if (compareAndSetTail(pred, node)) {// 通过cas 操作将链表的尾节点指向封装 好的线程3node 节点
                pred.next = node; // 此时把原链表中最后一个节点的下一节点指向封装好的线程3 node 节点
                return node; // 返回线程3 node 节点
            }
        }
        enq(node);
        return node;

此时链表结构如下图:
在这里插入图片描述

返回封装好后的线程node 节点 后进入acquireQueued 方法:获取锁,如果失败则挂起当前线程

final boolean acquireQueued(final Node node, int arg) {// node:传入封装好的线程node节点
// arg : 1
        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 前置节点的next 置空
                    failed = false;// 获取锁成功
                    return interrupted;// 返回当前线程是否被中断过 false
              // 此种情况出现在线程2 进入被锁的代码后,被封装到双向链表中以后,// 再一次获取锁,并且获取锁成功,就拿掉原有的链表头节点,并且把线程2 设置为链表// 的头节点 并且返回false
                }
                // 前置节点不为头节点或者 为头节点但是获取锁失败
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
 
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

获取链表的当前节点的前置节点:node.predecessor():

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

设置头节点 setHead(Node node):

 private void setHead(Node node) {
        head = node; // 设置当前节点为头节点
        node.thread = null;// 当前节点的 线程置为空
        node.prev = null;// 当前节点的前置节点置空
    }

setHead 和 p.next = null; 实际上将 原有的头节点 舍弃, 使用gc 进行回收
获取锁失败后应该阻塞当前线程:shouldParkAfterFailedAcquire(p, node):

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  // pred: 当前节点的前置节点, node:当前节点
        int ws = pred.waitStatus;// 获取前置节点的waitStatus 值,初始化的节点 都为0
        if (ws == Node.SIGNAL)//  Node.SIGNAL 为 -1  此时0 == -1
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;// 如果前置节点的值为-1 则可以大胆的阻塞当前线程
        if (ws > 0) {// 只有当waitStatus 为 CANCELLED 状态值是为 1 其余状态都小于 0
// 此时如果前置节点为取消的状态时
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;// 将node 的前置节点指向其原有前置节点// 的前置节点,相当于将当前节点的前置节点进行了移除
            } while (pred.waitStatus > 0);// 在检查新的前置节点是否为 CANCELLED
// 此do while 循环相当于把从后先前把所有状态值为CANCELLED  节点进行了移除
            pred.next = node;// 将前置节点的下一个节点指向 当前节点
// 什么时候会遇到ws > 0的case呢?当pred所维护的获取请求被取消时(也就是node的//waitStatus 值为CANCELLED),这时就会循环移除所有被取消的前继节点pred,直到找到// 未被取消的pred。移除所有被取消的前继节点后,直接返回false
        } else {//  CONDITION = -2  PROPAGATE = -3 和 0
            /*
             * 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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);// cas 操作将当前节点的前置节点waitStatus置为 -1
        }
        return false;
}

最开始封装的node 节点waitStatus 都是初始化的值0,由于shouldParkAfterFailedAcquire()将前置节点的waitStatus 修改为-1后返回false,会继续进行循环。
假设node的前继节点pred仍然不是头结点或锁获取失败,则会再次进入shouldParkAfterFailedAcquire() ,上一轮循环中,已经将pred.waitStatus设置为SIGNAL==-1,则这次会进入第一个判断条件,直接返回true,表示应该阻塞;
此时链表的结构可以如下:只有链表最后一个节点的waitStatus 为 0 其余节点都为-1
在这里插入图片描述显然,一旦shouldParkAfterFailedAcquire返回true也就是应该阻塞,就会执行parkAndCheckInterrupt() 阻塞并且检查是否中断,其中parkAndCheckInterrupt()调用了LockSupport.park(),该方法使用Unsafe类将线程挂起,等待后续唤醒(和await()有点类似),唤醒后就会再次循环acquireQueued里的请求逻辑;

 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);// 
        return Thread.interrupted();// 返回当前线程是否被中断状态 如果线程在park 期间被 interrupt() 打断则此时会返回true,并将线程是否打断状态复位为false
}

当前线程在执行acquireQueued过程中抛出了异常,导致线程中止前此时failed = true,会执行cancelAcquire方法(),(从代码角度看只有node.predecessor()方法和tryAcquire(arg)方法会主动抛出异常)将线程的状态改为CANCELLED

finally {
            if (failed) // 
                cancelAcquire(node);
        }

cancelAcquire:如果当前线程发生异常进入此方法,从当前节点向前遍历,移除取消状态的节点;

  private void cancelAcquire(Node node) { // 参数node : 当前节点
        // Ignore if node doesn't exist
        if (node == null) // 判断当前节点是否为空
            return;

        node.thread = null;// 将当前节点thread 置为空

        // Skip cancelled predecessors
        Node pred = node.prev;// 获取当前节点的前置节点
        while (pred.waitStatus > 0) // 当前置节点为取消状态
            node.prev = pred = pred.prev;// 移除链表中为取消状态的node 节点

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        Node predNext = pred.next; //  获取前置节点的下一个节点

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        node.waitStatus = Node.CANCELLED;// 把当前节点的状态置为 取消状态 1

        // If we are the tail, remove ourselves.
        if (node == tail && compareAndSetTail(node, pred)) { 
        	// 如果当前CANCELLED状态的节点是尾节点则,将当前链表的尾节点指向当前节点的前置节点
            compareAndSetNext(pred, predNext, null);// 设置当前节点的前置节点的下一个节点为null
          
        } 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 ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                // 如果是中间节点
                Node next = node.next;// 获取当前节点的下一节点
                if (next != null && next.waitStatus <= 0)
                	// 移除当前节点
                    compareAndSetNext(pred, predNext, next);
            } else {
            	// 如果移除CANCELLED状态的节点后,如果是头节点,则改双向链表穆目前只有一个节点
            	// 则唤醒改线程让其尝试去抢占锁
                unparkSuccessor(node);
            }

            node.next = node; // help GC 将当前节点的下一节点指向自己
        }
}

acquireQueued方法 返回线程是否被打断的标识,如果线程在挂起的过程中被interrupt() 打断过,则会在acquire(int arg) 方法中进入 selfInterrupt(); 方法设置线程被打断的标识:

	/**
     * Convenience method to interrupt current thread.
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

2.2.2 lock 公平锁获取:
公平锁也使用lock 方法去获取锁:

   final void lock() {
            acquire(1);
        }
   public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

进入tryAcquire(arg) 去尝试获取锁:

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();// 获取当前线程
            int c = getState();// 当前锁资源 state 数值
            if (c == 0) {// 如果为0 则表示当前资源没有被加锁,可以尝试去获取锁
                //如果当前阻塞队列为空,或者当前线程位于阻塞队列的第一个节点则可以去尝试获取锁。
                // compareAndSetState 将state 从0替换为1 替换成功则标识获取到锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    // 获取到锁后标识当前线程获取到了锁
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                // 如果发现当前获取锁的线程,已经获取到了锁则直接 将state 值+1 ,标识锁的重入
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

hasQueuedPredecessors:

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; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        // 返回true 的条件: 当头结点和尾节点指向非同一个节点(即阻塞队列不为空) 并且 (头结点的下一节点为空 或者 头节点的下一节点对应的现车非当前线程)
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

如果获取锁失败,则同非公平锁相同执行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg));封装当前线程成为一个node 节点,然后再次尝试去获取锁,如果依然获取锁失败则进存入到双向链表结构的阻塞队列,并且挂起当前线程,等待后续被唤醒然后在此获取锁;

2.2.3 tryLock():

 public boolean tryLock() {
 		// 可以看到此时使用非公平锁立即尝试去获取锁
        return sync.nonfairTryAcquire(1);
    }
  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;
        }

2.2.4 lock.tryLock(10,TimeUnit.SECONDS):

 public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

tryAcquire 方法线切尝试获取一次锁;如果获取锁成功则直接返回,如果失败则执行 doAcquireNanos(arg, nanosTimeout),在给定的时间内去尝试获取锁:

private boolean doAcquireNanos(int arg, long nanosTimeout)
		throws InterruptedException {
	if (nanosTimeout <= 0L)
		return false;
	// 获取当前自旋结束时间,通过当前纳秒数+超时纳秒数
	final long deadline = System.nanoTime() + nanosTimeout;
	// 封装当前线程为Node节点,并添加的AQS同步队列队尾
	// 此处注意Node节点的waitStatus状态为EXCLUSIVE,即表示独占
	final Node node = addWaiter(Node.EXCLUSIVE);
	// 是否获取到锁标志位
	boolean failed = true;
	try {
		for (;;) {
			// 获取当前节点的上一个节点
			final Node p = node.predecessor();
			// 如果上一个节点为head节点,并且继续尝试获取锁成功,则当前线程获取到锁
			// FIFO队列默认存在一个head节点(不存在则第一次会创建),该头结点为空
			if (p == head && tryAcquire(arg)) {
				// 获取到锁后,将当前节点置为头结点,并清空头节点内容
				setHead(node);
				// 将头节点的next指向置为空,处理后则p节点挂空,会被GC回收
				p.next = null; // help GC
				// 设置failed状态为false,说明竞争锁成功
				failed = false;
				return true;
			}
			// 如果没有获取到锁,则用过期时间减去当前时间,判断剩余时间是否合法
			nanosTimeout = deadline - System.nanoTime();
			// 如果剩余时间小于0,时间不合法,说明线程未获取到锁
			if (nanosTimeout <= 0L)
				return false;
			// 剩余时间>0时处理
			// shouldParkAfterFailedAcquire 判断当前节点是否可以挂起,
			// nanosTimeout > spinForTimeoutThreshold 表示如果线程剩余时间大于1000纳秒,则线程直接挂起nanosTimeout纳秒
			if (shouldParkAfterFailedAcquire(p, node) &&
				nanosTimeout > spinForTimeoutThreshold)
				LockSupport.parkNanos(this, nanosTimeout);
			if (Thread.interrupted())
				throw new InterruptedException();// 如果线程在挂起期间被打断则 抛出打断异常
		}
	} finally {
		// 如果自旋完成后,已经没有获取到锁,则将当前节点waitStatus置为失效
		if (failed)
			cancelAcquire(node);// 移除CANCELLED 状态节点
	}
}

2.2.4 lockInterruptibly() 可以响应中断的获取锁

  public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
   public final void acquireInterruptibly(int arg)
           throws InterruptedException {
       if (Thread.interrupted())
           throw new InterruptedException();
          // 先尝试去获取一次锁,如果成功则直接返回
          // 如果失败则执行 doAcquireInterruptibly,封装node 节点到阻塞队列中,在获取锁失败的时候,挂起现车
       if (!tryAcquire(arg))
           doAcquireInterruptibly(arg);
   }

doAcquireInterruptibly():

private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        // 封装node 节点到双向链表中
        final Node node = addWaiter(Node.EXCLUSIVE);
        // 获取锁成功标识
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();// 获取当前节点的前置节点
                if (p == head && tryAcquire(arg)) {// 如果前置节点为首节点,则尝试去获取锁
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                // 没有获取锁则 去挂起线程,如果线程在挂起过程中被interrupt() 打断则抛出中断异常
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);// 将取消状态的线程从链表中移除
        }
    }

parkAndCheckInterrupt():

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//  挂起当前线程
        return Thread.interrupted();// 返回现车是否被打断标识
    }

2.3 lock.unlock() 释放锁:

  public void unlock() {
        sync.release(1);
    }
   public final boolean release(int arg) {
        if (tryRelease(arg)) {// 尝试去释放锁
        	// 释放锁成果
            Node h = head;
            if (h != null && h.waitStatus != 0)// 如果头节点waitStatus 不为0(即阻塞队列存在需要获取锁的线程)
                unparkSuccessor(h);// 去唤醒一个线程去抢占锁
            return true;
        }
        return false;
    }

tryRelease():

 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);// 设置锁的state 值
            return free;
        }

unparkSuccessor(h):

private void unparkSuccessor(Node 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;// 头节点的waitStatus 不为0 设置为0
        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;
            // 从后向前遍历去获取一个可以去抢占锁的线程节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);// 唤醒线程去抢占锁
    }

3 总结:
3.1 lock 获取锁是否公平,只体现在获取锁的时候是否允许线程插队,如果一个线程在去获取锁的时候,立即去尝试获取锁,而不管是否已经存在因为获取不到锁而阻塞的队列,则为非公平锁;在释放锁的时候,都是从阻塞队列中唤醒一个等待时间最长的线程去抢占锁;

3.2 lock 锁中使用了大量的cas 比较和交换 操作来避免线程的直接阻塞;

4 扩展:
4.1 线程中断:Thread interrupt() ;Thread.currentThread().isInterrupted();Thread.interrupted();

  • Thread.currentThread().interrupt():表示为当前线程打上中断的标记,并且唤醒阻塞中的线程,继续向下执行;
  • Thread.interrupted():表示清除中断标记,如果当前线程中断,返回true,否则返回false;
  • Thread.currentThread().isInterrupted():表示查看当前线程的状态是否中断,不清除中断标记,如果当前线程中断,返回true,否则返回false;
    示例代码,可以看出当线程因为获取锁失败而被阻塞时,调用 interrupt() 方法后 会唤醒park 的线程,并在线程t2 获取到锁后,得到其中断的标识为true,
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description TODO
 * @Date 2022/10/28 14:43
 * @Author lgx
 * @Version 1.0
 */
public class ReentrantLockTest {
    static Lock lock = new ReentrantLock(true);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                lockTestA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            try {
                lockTestB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        t1.start();
        t2.start();
        t2.interrupt();

    }


    private static void lockTestA() throws InterruptedException {
        lock.lock();
        try {

            Thread.sleep(100000);
            Thread thread = Thread.currentThread();
            System.out.println("thread.isInterrupted() = " + thread.getName() + "," + thread.isInterrupted());
            if (thread.isInterrupted()) {
                System.out.println("thread.getName(): 中断过 = " + thread.getName());
            }
        } catch (InterruptedException ex) {

        } finally {
            lock.unlock();
        }

    }

    private static void lockTestB() throws InterruptedException {
        lock.lock();
        try {
            Thread thread = Thread.currentThread();
            System.out.println("thread.isInterrupted() = " + thread.getName() + "," + thread.isInterrupted());
            if (thread.isInterrupted()) {
                System.out.println("thread.getName(): 中断过 = " + thread.getName());
            }
        } catch (Exception ex) {
            System.out.println("ex.getMessage() = " + ex.getMessage());
        } finally {
            lock.unlock();
        }


    }

  
}

在这里插入图片描述
4. 2 CAS 比较和更新:
Compare And Swap 比较和交换,是用于实现多线程同步的原子指令 。将给定的值与内存位置上的内容进行比较,只有在相同的情况下,才将给的新值写入到内存位置上;
CAS的使用:
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B;
示例:

  • 在内存地址V当中,存储着值为10的变量
    在这里插入图片描述
  • 此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11
    在这里插入图片描述
  • 在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11
    在这里插入图片描述
  • 线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败
    在这里插入图片描述
  • 线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋
    在这里插入图片描述
  • 线程1进行SWAP,把地址V的值替换为B,也就是12
    在这里插入图片描述
  • 线程1进行SWAP,把地址V的值替换为B,也就是12
    -
    CAS 优缺点:
    优点:
    在并发量不是很高时cas机制会提高效率;
    缺点:
  • .CPU开销较大,在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
  • 不能保证代码块的原子性,CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
    -ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

参考:Java:CAS(乐观锁)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值