J.U.C — Locks — ReentrantLock(一)

2024-03-14 17:48
文章标签 locks reentrantlock

本文主要是介绍J.U.C — Locks — ReentrantLock(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

整体结构



公平锁和非公平锁

  如果获取一个锁是按照请求的顺序得到的,那么就是公平锁,否则就是非公平锁。
     公平锁保证一个阻塞的线程最终能够获得锁,因为是有序的,所以总是可以按照请求的顺序获得锁。
     不公平锁意味着后请求锁的线程可能在其前面排列的休眠线程恢复前拿到锁,这样就有可能提高并发的性能。
     这是因为通常情况下挂起的线程重新开始与它真正开始运行,二者之间会产生严重的延时。因此非公平锁就可以利用这段时间完成操作。这是非公平锁在某些时候比公平锁性能要好的原因之一。
  • 初始化
public ReentrantLock() {sync = new NonfairSync();     // 默认是非公平锁
}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}
  • 公平锁 和 非公平锁 : Lock
ReentrantLock lock = new ReentrantLock(true); // 声明公平锁
lock.lock();
try {// method body
} finally {lock.unlock();
}lock.lock() -->// 公平锁: ReentrantLock.FairSyncfinal void lock() {acquire(1);}// 非公平锁: ReentrantLock.NonfairSync(Sync默认)final void lock() {if (compareAndSetState(0, 1))     // 一定程度上不够公平setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}-->// AQSpublic final void acquire(int arg) {if (!tryAcquire(arg) &&     // 1. 如果tryAcquire(arg)成功,那就没有问题拿到锁;若失败则2acquireQueued(addWaiter(Node.EXCLUSIVE), arg))     // 2. 创建一个独占的节点(Node.EXCLUSIVE),并插入CLH队尾;自旋获取锁selfInterrupt();     // 3. 因为在获得锁之前线程会被中断,这个地方会清楚中断标记位}-->公平锁1.protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();     // 获取当前锁的状态if (c == 0) {     // 若为0,说明没有线程持有锁if (!hasQueuedPredecessors() &&     // 若有先序节点,退出;若无则获取锁。 确保公平性,即前面的节点先获取锁compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {     // 若持有锁的线程就是当前线程,则直接state++int nextc = c + acquires;     if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}非公平锁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) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}-->2.自旋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)) {     // 如果当前节点是AQS队列的头结点(如果第一个节点是DUMP节点也就是傀儡节点,那么第二个节点实际上就是头结点了),就尝试在此获取锁tryAcquire(arg)。如果成功就将头结点设置为当前节点(不管第一个结点是否是DUMP节点),返回中断位setHead(node);p.next = null; // help GC ?没有把node的prev指针设为null,怎么GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}Node node = new Node(Thread.currentThread(), mode);Node pred = tail;if (pred != null) {     // 若队列不为空,则将当前节点设为tailnode.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))     // Dump Nodetail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}/*** CANCELLED = 1: 节点操作因为超时或者对应的线程被interrupt。节点不应该留在此状态,一旦达到此状态将从CHL队列中踢出。* SIGNAL = -1: 节点的继任节点是(或者将要成为)BLOCKED状态(例如通过LockSupport.park()操作),因此一个节点一旦被释放(解锁)或者取消就需要唤醒(LockSupport.unpack())它的继任节点。* CONDITION = -2:表明节点对应的线程因为不满足一个条件(Condition)而被阻塞。* 0: 正常状态,新生的非CONDITION节点都是此状态。*/private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)return true;if (ws > 0) {     // 如果前一个节点的等待状态waitStatus>0,也就是前一个节点被CANCELLED了,那么就将前一个节点去掉,递归此操作直到所有前一个节点的waitStatus<=0do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {     //  前一个节点等待状态waitStatus=0,修改前一个节点状态位为SINGAL,表示后面有节点等待你处理compareAndSetWaitStatus(pred, ws, Node.SIGNAL);     // 这个地方没有返回true,即中断,是为了执行下一次循环先尝试获取锁return false;}// 3. static void selfInterrupt() {Thread.currentThread().interrupt();}

公平锁和非公平锁:UnLock

lock.unlock(); -->// AQSpublic final boolean release(int arg) {if (tryRelease(arg)) {     // 1. 先尝试释放Node h = head;if (h != null && h.waitStatus != 0) // 这个是前面将节点状态设为signal的原因,如果不设置的话,在这个地方释放锁后不会唤醒后继节点unparkSuccessor(h);     // 2. 唤醒后继节点return true;}return false;}// ReentrantLock.Syncprotected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())     // 判断持有线程的锁是不是当前线程,若不是,则报异常throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {          // 若状态位为0,表示没有线程持有锁,将独自线程清空;若不等于0,可能是一个线程多次获取锁,因为ReentrantLock是可重入锁,所以不等于0的时候,只是将状态位减少,并不清空独占线程,也不唤醒下一个节点free = true;setExclusiveOwnerThread(null);}setState(c);return free;}private void unparkSuccessor(Node node) {int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);Node s = node.next;     // 很关键,第一次释放锁的时候,Dump节点在这被跳过去的if (s == null || s.waitStatus > 0) {s = null;for (Node t = tail; t != null && t != node; t = t.prev)     // 从tail -> head 找到最后一个有效的节点if (t.waitStatus <= 0)s = t;}if (s != null)LockSupport.unpark(s.thread);     // 唤醒节点,唤醒之后的节点会进入acquireQueued操作}

举例

假设有3个线程陆续执行lock.lock():
  1.     开始状态:
state = 0, 所有的线程都停留在lock前,这里的CPU是指正在执行lock与unlock之间的任务的线程


  1.       thread-1执行lock,由于当前没有线程正在运行,所以thread-1获取锁,直接开始运行,并将state设置1和独占线程设置为thread-1
  1.       thread-2执行lock:
由于当前state为1且是thread-1,故而thread-2进入sync队列
当前sync队列为空,先创建Dump节点,并将head和tail设置为Dump节点;
创建thread-2的节点,并插入队列尾部,并将tail设置为thread-2;
thread-2节点自旋获取锁,同时因为前驱节点的waitstatus为0,更改为-1(Signal),在自旋循环的下一次若还无法获取锁,则中断

  1.       thread-3执行lock:
由于当前state为1且是thread-1,故而thread-3进入sync队列
当前队列不为空,直接将创建的thread-3插入队列,并将tail设置为thread-3;
thread-3节点自旋获取锁,同时由于前驱节点的waitstatus为0,设置为-1(Signal),在自旋循环的下一次若还无法获取锁,则中断


  1.       若此时threa-1再次执行lock, 只会将state增加1,同时之后释放锁也需要执行相应次数的unlock操作。threa-1执行unlock:
从head.next节点开始遍历,找到第一个waitstatus <= 0的节点唤醒,在这里Dump节点感觉相当于正在运行的线程thread-1;
此时thread-2被唤醒,并执行acquired获取锁,并将Head设置为thread-2

  1.     thread-2执行unlock:

  1.     thread-3执行unlock:
因为thread-3节点的waitStatus为0,故而不会唤醒下一个节点


这篇关于J.U.C — Locks — ReentrantLock(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/809200

相关文章

java线程 yield,sleep,join,synchronized wait notify notifyAll,ReentrantLock lock condition, 生产者消费者

yield,sleep,join yield,join,sleep,join是Thread中的方法,不需要 在synchronized 代码块中调用,和synchronized 没关系,也不会释放锁。 Thread.sleep(100);Thread.yield();Thread t;t.join(); (1)yield()不一定保证让出cpu yield()只是使当前线程重新回

synchronized wait()/notify 对比 ReentrantLock await()/signal()

结论 synchronized synchronized 配合 wait()/notify 无法实现精准唤醒线程 ReentrantLock ReentrantLock 配合 Condition await()/signal() 可以实现精准唤醒线程 (指唤醒指定的线程) ReentrantLock 如何实现精准唤醒线程 一个 lock 配合多个 Condition, 且

ReentrantLock的lockInterruptibly()理解

ReentrantLock锁有几种:lock、tryLock、tryLock(long timeout, TimeUnit unit)、lockInterruptibly。 lock 阻塞等待获取锁,优先考虑获取锁,待获取锁成功后,才响应中断。 lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。  tryLock() 是一个有返回值的方法,试图申请一个锁

【并发】Lock与ReentrantLock

1 Lock基本使用 Lock能实现代码同步,它比synchronized更具灵活性,什么时候锁住,什么时候释放锁等都是看得见的,使用时必须使用try{}finally{},意思是万一发生异常或者错误都可以释放锁。 try{}finally{//释放锁} 使用示例 public class SaleTicket implements Runnable {private int tic

ReentrantLock可重入锁又是怎么回事?

前言:有关Synchronized锁的知识可以参考我上篇写的内容synchronized必知必会的知识点 一:ReentrantLock的实现原理 锁的实现原理基本是为了达到一个目的:让所有的线程都能看到某种标记。Synchronized通过在对象头中设置标记实现了这一目的,是一种/原生的锁实现方式,而ReentrantLock以及所有的基于LocK接口的实现类,都是通过用一个volitl

Java并发之ReentrantLock详解

原文: http://blog.csdn.net/lipeng_bigdata/article/details/52154637      一、入题         ReentrantLock是Java并发包中互斥锁,它有公平锁和非公平锁两种实现方式,以lock()为例,其使用方式为: [java] view plain copy ReentrantLoc

除了Synchronized和ReentrantLock外,有哪些别的同步机制

除了synchronized和ReentrantLock外,有哪些别的同步机制 答案: Java提供了多种同步机制和锁用来满足不同的并发需求。除synchronized和ReentrantLock之外,还有以下几种: 1. CountDownLatch(倒计时器):允许一个或多个线程等待其他线程完成操作。它维护了一个计数器,表示需要等待的事件数量。每当一个事件完成时,计数器减一。 当计数器

“J.U.C”:ReentrantLock之三unlock方法分析

前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获取锁。但是对于unlock()而已,它是不分为公平锁和非公平锁的。 [java]  view plain copy public void unlock() {

“J.U.C”:ReentrantLock之一简介

注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述。本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的lock、unlock实现机制。 ReentrantLock,可重入的互斥锁,是一种递归无阻塞的同步机制。它可以等同于synchronized的使用,但是ReentrantLock提供了比sync

7、深入理解AQS之独占锁ReentrantLock

深入理解AQS之独占锁ReentrantLock 管程 - Java同步的设计思想MESA模型 AQS原理分析什么是AQSAQS核心结构AQS定义两种队列同步等待队列条件等待队列 基于AQS实现一把独占锁 ReentrantLock源码分析ReentrantLock原理lock()流程图unlock()流程图 ReentrantLock源码分析构造函数lock()加锁公平锁非公平锁acqui