广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java并发编程之浅谈ReentrantLock
  • 960
分享到

Java并发编程之浅谈ReentrantLock

2024-04-02 19:04:59 960人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录一、首先看图二、lock()跟踪源码2.1 非公平锁实现2.1.1 tryAcquire(arg)2.1.2 acquireQueued(addWaiter(node.EXCLU

一、首先看图

在这里插入图片描述

二、lock()跟踪源码

在这里插入图片描述

这里对公平锁和非公平锁做了不同实现,由构造方法参数决定是否公平。


public ReentrantLock(boolean fair) {
     sync = fair ? new FairSync() : new NonfairSync();
}

2.1 非公平锁实现


static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

代码量很少。首先compareAndSetState(0, 1)通过CAS(期望值0,新值1,内存值stateOffset)

  • 如果修改成功,即抢占到锁,setExclusiveOwnerThread(Thread.currentThread());将AQS中的变量exclusiveOwnerThread设置为当前抢占到锁的线程,也就是图中的ThreadA。
  • 若没有抢占成功,证明此时锁被占用,执行方法acquire(1);

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

这里主要看两个方法tryAcquire(arg)acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。当满足if条件后,会给当前线程标记一个interrupt状态。

2.1.1 tryAcquire(arg)

这个方法又有多个实现。这里看NonfairSync非公平锁。

在这里插入图片描述


protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

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

在这个方法中,还不死心,首先会判断下AQS中的state是否为0,为0也就是说距离上次尝试获取锁到现在准备进入队列(双向链表)中这段时间内,锁已经被释放,可以重新CAS尝试获取锁。

如果当前锁还是被持有状态,就是state!=0,就会判断,当前线程是不是当前持有锁的线程exclusiveOwnerThread,如果是,则state+1,从这里可以看出state表示的是重入次数。

全部不满足,返回false。

2.1.2 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

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);
    return node;
}

tryAcquire(arg)返回false,证明当前线程还是没有获取到锁。那么就要进入队列等待了,首先addWaiter方法,将当前线程封装成一个Node,如果pred不为空,则将当前节点做链表的尾部插入,同时为了防止在此期间前序节点已经不在队列中了,也会运用CAS操作来执行(期望值pred,新值node,内存值tailOffset)。

如果前序节点为空,或者在CAS时发现前序节点已经不存在了,则重新构建链表,将当前节点封装的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;
            }
        }
    }
}

加入完成后,返回当前node节点,进入acquireQueued方法。

acquireQueued


final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        	//获取到当前node节点的上一个节点
            final Node p = node.predecessor();
            //如果当前的上个节点就是头节点,会再次尝试获取锁
            if (p == head && tryAcquire(arg)) {
            	//获取成功,将当前节点置空,并成为新的头节点
                setHead(node);
				//这个p已经没用了,防止内存泄漏,直接指向null,下次GC时回收
                p.next = null; // help GC
                //不需要取消
                failed = false;
                //return false,不需要中断当前线程
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

这里是一个自旋操作,首先拿到当前线程封装节点的上一个节点,如果满足第一个if条件if (p == head && tryAcquire(arg)),证明上个节点为头节点,则此时当前线程也会再次尝试获取锁,获取锁成功,证明此时没有别的线程在队列中了,则将当前node清空并设置为头节点,返回不需要中断当前线程。

在第二个if条件中if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())。走到这里证明当前线程不是第一个线程节点,或者没有抢占到锁,shouldParkAfterFailedAcquire这个方法见名知意,在抢占失败后是否需要park阻塞,里面主要是用于清理双向链表中被取消的节点线程和未被阻塞的节点线程。


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;//获取前置节点的等待状态
    if (ws == Node.SIGNAL)
		//前置节点的等待状态为-1,表示前置节点在队列中阻塞,那么当前节点也需要被阻塞在队列中
        return true;
    if (ws > 0) {
		//前置节点等待状态大于0,此前置节点已经被取消,循环遍历清除所有已被取消的节点。
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
		//前置节点等待状态小于等于0,且不等于-1,也就是没有被阻塞也没有被取消
		//则将前置节点设置为阻塞状态。
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
  • 前置节点的等待状态为-1,表示前置节点在队列中阻塞,那么当前节点也需要被阻塞在队列中
  • 前置节点等待状态大于0,此前置节点已经被取消,循环遍历清除所有已被取消的节点。
  • 前置节点等待状态小于等于0,且不等于-1,也就是没有被阻塞也没有被取消。则将前置节点设置为阻塞状态。

到这里,基于非公平锁的实现结束。

2.2 公平锁实现

公平锁和乐观锁的区别就在于,非公平锁acquire(1)前会先尝试获取锁,公平锁直接acquire(1)


static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
}

2.2.1 tryAcquire(arg)

在tryAcquire中也和非公平锁有一定的区别。在当前锁没有被占有时。非公平锁不用考虑目前AQS队列中的排队情况,直接通过CAS尝试获取锁。公平锁会看目前队列的状态,再来决定是尝试占有锁还是在队列中等待。


protected final boolean tryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
       if (!hasQueuedPredecessors() &&
           compareAndSetState(0, acquires)) {
           setExclusiveOwnerThread(current);
           return true;
       }
   }
   else if (current == getExclusiveOwnerThread()) {
       int nextc = c + acquires;
       if (nextc < 0)
           throw new Error("Maximum lock count exceeded");
       setState(nextc);
       return true;
   }
   return false;
}

到此这篇关于Java并发编程之浅谈ReentrantLock的文章就介绍到这了,更多相关Java ReentrantLock内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java并发编程之浅谈ReentrantLock

本文链接: https://www.lsjlt.com/news/126800.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java并发编程之浅谈ReentrantLock
    目录一、首先看图二、lock()跟踪源码2.1 非公平锁实现2.1.1 tryAcquire(arg)2.1.2 acquireQueued(addWaiter(Node.EXCLU...
    99+
    2022-11-12
  • 浅谈JAVA并发之ReentrantLock
    目录1. 介绍2. 源码剖析2.1 上锁(获取资源)2.2 释放资源2.3 公平锁与非公平锁的区别1. 介绍 结合上面的ReentrantLock类图,ReentrantLock实...
    99+
    2022-11-12
  • 浅谈Java并发中ReentrantLock锁应该怎么用
    目录1、重入锁说明2、中断响应说明3、锁申请等待限时tryLock(long, TimeUnit)tryLock()4、公平锁说明源码(JDK8)重入锁可以替代关键字 synchro...
    99+
    2022-11-12
  • Java多线程并发之ReentrantLock
    目录ReentrantLock公平锁和非公平锁重入锁小结疑惑ReentrantLock 公平锁和非公平锁 这个类是接口 Lock的实现类,也是悲观锁的一种,但是它提供了 lock和 ...
    99+
    2023-05-18
    Java 多线程并发 Java ReentrantLock
  • 浅谈Java并发之同步器设计
    前言: 在 Java并发内存模型详情了解到多进程(线程)读取共享资源的时候存在竞争条件。 计算机中通过设计同步器来协调进程(线程)之间执行顺序。同步器作用就像登机安检人员一样可以协...
    99+
    2022-11-12
  • 【漫画】JAVA并发编程 J.U.C Lock包之ReentrantLock互斥锁
    在如何解决原子性问题的最后,我们卖了个关子,互斥锁不仅仅只有synchronized关键字,还可以用什么来实现呢? J.U.C包中还提供了一个叫做Locks的包,我好歹英语过了四级,听名字我就能马上大声的说:Locks包必然也可以...
    99+
    2018-12-24
    【漫画】JAVA并发编程 J.U.C Lock包之ReentrantLock互斥锁
  • 浅谈Swoole并发编程的魅力
    目录场景介绍并发编程编码实现并发难题数据同步问题思维转变场景介绍 假设我们要做一个石头剪刀布的Web游戏,3个玩家同时提交竞猜后显示胜者。在传统串行化Web编程中,我们一般思路是这样...
    99+
    2022-11-12
  • Java 多线程并发ReentrantLock
    目录背景ReentrantLock可重入特性公平锁设置参数源码分析Lock 接口加锁操作内部类SynctryLockinitialTryLocklocklockInterruptib...
    99+
    2022-11-13
  • 浅谈一下Java的线程并发
    谈到并发,必会涉及操作系统中的线程概念,线程是CPU分配的最小单位,windows系统是抢占式的,linux是轮询式的,都需要获取CPU资源。并行:同一时刻,两个线程都在执行。并发:...
    99+
    2022-11-13
  • Java并发编程之ReentrantLock实现原理及源码剖析
    目录一、ReentrantLock简介二、ReentrantLock使用三、ReentrantLock源码分析1、非公平锁源码分析2、公平锁源码分析前面《Java并发编程之JUC并发...
    99+
    2022-11-12
  • 怎么浅谈Java并发编程中的Java内存模型
    这篇文章的内容主要围绕怎么浅谈Java并发编程中的Java内存模型进行讲述,文章内容清晰易懂,条理清晰,非常适合新手学习,值得大家去阅读。感兴趣的朋友可以跟随小编一起阅读吧。希望大家通过这篇文章有所收获!物理计算机并发问题在介绍Java内存...
    99+
    2023-06-17
  • java并发编程中ReentrantLock可重入读写锁
    目录一、ReentrantLock可重入锁二、ReentrantReadWriteLock读写锁三、读锁之间不互斥一、ReentrantLock可重入锁 可重入锁ReentrantL...
    99+
    2022-11-13
  • 怎么在Java中使用ReentrantLock实现并发编程
    这篇文章给大家介绍怎么在Java中使用ReentrantLock实现并发编程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、首先看图二、lock()跟踪源码这里对公平锁和非公平锁做了不同实现,由构造方法参数决定是否公...
    99+
    2023-06-15
  • 浅谈Java 并发的底层实现
    并发编程的目的是让程序运行更快,但是使用并发并不定会使得程序运行更快,只有当程序的并发数量达到一定的量级的时候才能体现并发编程的优势。所以谈并发编程在高并发量的时候才有意义。虽然目前还没有开发过高并发量的程序,但是学习并发是为了更好理解一些...
    99+
    2023-05-30
    java 并发 底层
  • Java并发编程之threadLocal
    目录1、ThreadLocal介绍2、ThreadLocal使用实例3、ThreadLocal实现原理1、ThreadLocal介绍 多个线程访问同一个共享变量时特别容易出现并发问题...
    99+
    2022-11-12
  • Java多线程并发ReentrantLock怎么使用
    这篇文章主要介绍“Java多线程并发ReentrantLock怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java多线程并发ReentrantLock怎么使用”文章能帮助大家解决问题。背景...
    99+
    2023-07-02
  • 浅谈并发处理PHP进程间通信之System V IPC
    目录前言Unix System V IPC信号量共享内存消息队列函数介绍ftoksemaphore函数shared_memory函数代码实现小结前言 它的安装和使用非常简单,在编译 ...
    99+
    2022-11-12
  • Java 浅谈 高并发 处理方案详解
    目录高性能开发十大必须掌握的核心技术I/O优化:零拷贝技术I/O优化:多路复用技术线程池技术无锁编程技术进程间通信技术Scale-out(横向拓展)缓存异步高性能、高可用、高拓展 解...
    99+
    2022-11-12
  • 浅谈Java编程之if-else的优化技巧总结
    一、使用策略枚举来优化if-else 看到网上蛮多人推荐使用策略模式来优化if-else,但我总觉得,搞一堆策略类来优化大批量if-else,虽然想法很好,但无意之中很可能又会创造出...
    99+
    2022-11-12
  • 浅谈并发处理PHP进程间通信之外部介质
    目录进程间通信文件flock代码实现mysqlselect for update代码实现redisincrSETNX代码实现优化总结进程间通信 进程间通信,指至少两个进程或...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作