并发编程系列之重入锁VS读写锁

2023-11-02

前言

上节我们介绍了Java中的锁基础篇,也算是对锁有了个基本的认识,对锁底层的一些原理有所掌握,那么今天我们就来看看2个最常见的锁的实例应用,重入锁和读写锁,这是今天旅途最美的两大景点,是不是有点迫不及待了,OK,那就让我们一起开启今天的并发之旅吧,祝您今天的旅途愉快。

 

景点一:重入锁

什么是重入锁?

重入锁ReentrantLock指的是支持重进入的锁,表示该锁能够支持同一个线程对资源的重复加锁,也就是说当线程对某个资源获取锁之后,该线程继续获取该资源的锁时不会被阻塞;

synchronized关键字就是一种可重入锁,不过他是隐式的支持可重入,不需要开发者手动的获取锁,解锁,其实本质上synchronized锁机制实现同步本身就是隐式的,对开发者完全是透明的,这样虽然简化了开发,但是也限制了对锁的可控性;

ReentrantLock虽然没有像synchronized一样支持隐式的重进入,但是在调用了lock方法之后,已经获得锁的线程还能继续调用lock方法获得锁,而不会被自己阻塞;

 

锁获取公平性问题

什么是公平的获取锁:在绝对时间上,先对锁进行获取的请求一定会先获取到锁,那么这个锁就成为公平性锁,也就是说,等待时间越长的线程越优先获取锁,获取锁的顺序是跟请求顺序一致的,先到先得的规则,满足这种规则就是公平性锁,反正就是非公平性锁,ReentrantLock通过一个Boolean值来控制是否为公平性锁:

// 公平性可重入锁
ReentrantLock fairReentrantLock = new ReentrantLock(true);
// 非公平性可重入锁
ReentrantLock  unFairReentrantLock = new ReentrantLock(false);

 

公平性锁和非公平性锁比较:公平锁事实上没有非公平性锁效率高,主要是因为公平性锁每次都是从同步队列中的第一个节点获取锁(为了保证FIFO特性)。那么有人就会疑问,既然效率是非公平性高,那么我们是不是就应该尽量多的去使用非公平性锁代替公平性锁呢?当然答案是否定的,非公平性锁虽然效率上比较高,但是出现线程饥饿的情况概率比较大,这是因为刚刚释放锁的线程再次获得锁的概率比较大,这样就会导致某些线程会出现一直获取不到锁的机会,而一直在同步队列中等待着,也正是非公平锁的这种随机不公平性导致饥饿概率大大提升;

对于我们的ReentrantLock,其实还是更倾向于高效率的非公平性锁,我们看如下源码就能很清晰的看到这一点:

ReentrantLock  reentrantLock = new ReentrantLock();
public ReentrantLock() {
       // new 一个非公平性锁
       sync = new NonfairSync();
   }

公平性锁和非公平性锁总结:公平性锁保证了锁的获取按照FIFO规则进行,而代价是进行大量的线程切换,降低了处理效率;非公平性锁,虽然会造成线程饥饿情况发生,但是极少的线程切换,换来了更大的吞吐量,提高处理效率。

如何实现重进入

重进入是指任意线程在获取到锁之后还能再次获取该锁,而不会被阻塞,实现重进入主要需要解决下面2个问题:

  • 线程再次获取锁:锁需要去识别获取锁的线程是否为当前已经获得锁的线程,如果是,那就再次获取锁成功,不是则进入同步队列等待下次获取;

  • 锁的最终释放:同一个线程重复N次获取了锁,那么就需要在随后第N次释放锁之后,其他线程才能获取到该锁。其实现主要依赖于一个计数器,对于某个线程获取某个资源都有一个计数器,每次该线程再次获取该锁时计数器+1,而锁被释放则-1,当计数器为0时,才能代表真正释放成功,才能被其他线程获取;

上面我们提到ReentrantLock有2种锁,那么我们接下来就分别就这2种分析其获取和释放是如何实现的;

 

公平性锁的获取

fairReentrantLock.lock();

我们再看看公平性锁lock方法内部实现:其调用Sync的lock方法

public void lock() {
       sync.lock();
   }

我们在看下如何获取同步状态的:

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

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

       protected final boolean tryAcquire(int acquires) {
           final Thread current = Thread.currentThread();
           int c = getState();
           // 判断是否是重进入,如果不是重进入,首次获取锁,得先判断是有有前驱节点
           if (c == 0) {
                       // 判断当前线程节点在同步队列中是否有前驱节点,如果有
               // 则表示有线程比当前线程更早的请求获取锁,因此需要等待
               // 前驱线程获取并释放锁之后才能继续获取锁,如果没有前驱
               // 节点,并且CAS操作成功则说明获取同步状态成功,即获取锁成功,返回
               if (!hasQueuedPredecessors() &&
                   compareAndSetState(0, acquires)) {
                   setExclusiveOwnerThread(current);
                   return true;
               }
           }
           // 如果是重进入就直接获取锁成功,计数器+1
           else if (current == getExclusiveOwnerThread()) {
               int nextc = c + acquires;
               if (nextc < 0)
                   throw new Error("Maximum lock count exceeded");
               setState(nextc);
               return true;
           }
           return false;
       }
   }

 

公平性锁的释放

同样的在释放锁也需要根据计数器判断,先调用释放锁方法:

try {
           // do some thing
       }finally {
           fairReentrantLock.unlock();
       }

然后我们看下释放锁的源码:假设获取锁的次数为N次,则前N-1次都返回false,第N次才返回true,真正释放锁

protected final boolean tryRelease(int releases) {
           // 计数器-1
           int c = getState() - releases;
           if (Thread.currentThread() != getExclusiveOwnerThread())
               throw new IllegalMonitorStateException();
           boolean free = false;
           // 如果计数器=0,说明最终释放条件满足,设置当前同步状态为0
           // 并且将当前占有线程设置为null,并返回true
           if (c == 0) {
               free = true;
               setExclusiveOwnerThread(null);
           }
           setState(c);
           return free;
       }

 

非公平性获取锁

ReentrantLock  unFairReentrantLock = new ReentrantLock(false);
unFairReentrantLock.lock();

非公平性获取锁和公平性获取锁区别不大,主要体现在不需要判断同步队列中的当前线程需要有前驱节点,它不需要保证节点的FIFO,我们看源码分析:

final boolean nonfairTryAcquire(int acquires) {
           final Thread current = Thread.currentThread();
           // 判断是否是首次获取锁
           int c = getState();
           // 如果是首次获取锁,直接CAS操作获取锁成功,返回true
           if (c == 0) {
               if (compareAndSetState(0, acquires)) {
                   setExclusiveOwnerThread(current);
                   return true;
               }
           }
           // 否则,加锁计数器+1,获取锁成功,返回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;
       }

代码逻辑:判断当前线程是否为获取锁的线程,如果是获取锁的线程再次请求,则将同步状态计数器+1并返回true,获取锁成功,如果不是则表现当前线程是第一次获取锁,直接进行CAS修改同步状态,修改成功返回true,完成非公平性获取锁的操作,非公平性释放锁流程和源码跟公平性是一样的,都是tryRelease方法,就不做重复的讲解了。

 

景点二:读写锁

什么是读写锁?

上面所说的重入锁是排他锁,排他锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写操作时,所有对该资源的读线程和写线程都将被阻塞;

读写锁实质上是维护这一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性有很大的提升;

读写锁实现:在读操作的时候获取锁,写操作时获取写锁即可,当写锁被获取时,后续的读写操作都会阻塞,写锁释放之后,所有操作继续执行,而读锁是可以同时并发访问的;

 

读写锁的特性

读写锁比其他排他锁具有更好的并发性和吞吐量,主要有下面三大特性:

  • 公平性选择:支持公平和非公平的锁获取方式,吞吐量考虑非公平锁优先公平锁

  • 重进入:读写锁支持重进入

  • 锁降级:遵循写锁——读锁——释放写锁的次序,写锁也能降级为读锁

  • 支持中断:读取锁和写入锁都支持锁获取时的中断

  • Condition:写入锁提供了Condition的实现

 

读写锁的接口

ReadWriteLock接口只定义了两个方法,所以一般情况下,我们都是使用实现好的类ReentrantReadWriteLock,我们先看下ReadWriteLock定义的两个方法如下:

ReentrantReadWriteLock类提供的方法如下:

读写锁的实现原理

读写状态的设计

读写锁同样依赖自定义同步器来实现同步功能,读写状态就是其同步器的状态,读写锁的自定义同步器系统在同步状态上维护多个读线程和一个写线程的状态;

读写锁采用按位划分的思想,将一个整型变量切分成两个部分,高16位表示读,低16位表示写,如下图所示:

写锁的获取和释放

写锁是一个支持重进入的排他锁,如果当前线程已经获取写锁,则写状态+1,如当前线程在获取写锁的时候,读锁已经被获取或者获取写锁的不是当前线程,则当前线程进入等待状态,我们看源码如下:

/** 如果读计数器不为0,或者写计数器不为0,并且当前线程
*  不是已经获得锁的线程时,则失败;
*
*  如果计数器饱和,则失败;
*
*  否则当前线程就可以获得锁,要么为可重入的,要么就是
*  FIFO策略下的,如果满足,则更新同步状态,获取锁成功
*/
protected final boolean tryAcquire(int acquires) {
           Thread current = Thread.currentThread();
           int c = getState();
           int w = exclusiveCount(c);
           if (c != 0) {
               // 如果存在读锁或者当前线程不是已经获得写锁的线程时
               if (w == 0 || current != getExclusiveOwnerThread())
                   return false;
               if (w + exclusiveCount(acquires) > MAX_COUNT)
                   throw new Error("Maximum lock count exceeded");
               // Reentrant acquire
               setState(c + acquires);
               return true;
           }
           // 是否为读锁 或者CAS操作失败
           if (writerShouldBlock() ||
               !compareAndSetState(c, c + acquires))
               return false;
           setExclusiveOwnerThread(current);
           return true;
       }

我们会发现这里还增加了一个是否为读锁判断,如果存在读锁则写锁就不能被获取,这是什么原因呢?是因为:读写锁要保证写锁的操作对读锁是可见的,也就是说,每个写操作的结果一定要对后续所有的读操作是可见的。所以,写锁一定要等待其他读线程全部都释放了锁才能被当前线程获取,而写锁一旦被获取,则其他后续读写线程都必须被阻塞;

写锁的释放:写锁的释放与ReentrantLock的释放过程基本一样,每次释放时都减少写状态,当写状态计数器为0时,则最终完全释放,从而等待的读写线程才能够继续访问读写锁,并且当前释放的写操作结果对后续操作均可见。

读锁的获取和释放

读锁是一个支持可重入的共享锁,它能够被多个线程同时获取,在没有线程对资源访问时,读锁就会获取成功,增加读状态,如果在获取读锁的过程中,该资源的写锁已经被其他线程获取,则读锁进入等待状态,等待写锁的释放,再尝试获取读锁。

获取读锁的逻辑:如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态;如果当前线程获取写锁或者写锁没有被其他线程获取,则当前线程获取读锁成功,增加读状态,也就是说同一个线程可以在获取写锁的前提下再次获得该读锁,有人就会问,不是所有的写必须对后续读可见吗?万一这里写线程执行比读慢不就不满足写结果对读的可见了吗,这里我要做个说明:上面我在讲读写锁特性时提到过锁降级,必须遵循写锁——读锁——释放写锁的次序,也就是说同一个线程对同一个对象的操作,必须写优先于读,所以写的结果一定在读之前,不用去担心脏读的发生;具体源码见下:

protected final int tryAcquireShared(int unused) {
           Thread current = Thread.currentThread();
           int c = getState();
           if (exclusiveCount(c) != 0 &&
               getExclusiveOwnerThread() != current)
               return -1;
           int r = sharedCount(c);
           if (!readerShouldBlock() &&
               r < MAX_COUNT &&
               compareAndSetState(c, c + SHARED_UNIT)) {
               if (r == 0) {
                   firstReader = current;
                   firstReaderHoldCount = 1;
               } else if (firstReader == current) {
                   firstReaderHoldCount++;
               } else {
                   HoldCounter rh = cachedHoldCounter;
                   if (rh == null || rh.tid != current.getId())
                       cachedHoldCounter = rh = readHolds.get();
                   else if (rh.count == 0)
                       readHolds.set(rh);
                   rh.count++;
               }
               return 1;
           }
           return fullTryAcquireShared(current);
       }

其中SHARED_UNIT为:

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

 

读锁的释放:跟前面讲的都差不多,都是每次释放减少读状态,减少的值是1<<16,我们前面说了高16位为读状态。当减少若干次数高16位等于0时,则代表读锁全部释放完毕;

锁降级

锁降级指的是写锁降级为读锁,是线程先获取写锁——然后又获取了读锁——随后释放写锁的过程;如果是获取写锁——释放写锁——再获取读锁,这种分段完成的过程不能称之为锁降级;

有人就会问锁降级过程中,获取读锁的步骤可不可以省略,这当然是不行的,读锁在这里的目的是为了保证数据的可见性,假设线程1获取了写锁,然后释放写锁,而没有获取读锁,如果此时线程2刚好获取了写锁,就会把线程1更新的值直接覆盖掉,而对于其他线程完全不可见,也就是说线程1的修改完全没有被其他线程感知到;

读写锁是没有锁升级的,主要目的也是为了保证数据的可见性,如果某个对象读锁已经被多个线程获取,而此时其中一个线程升级为写锁了,那么该线程对数据的更新,对于剩下的其他获取到读锁的线程就是不可见的。

 

以上就是今天重入锁和读写锁两大景点的全部内容,是不是有种意犹未尽的感觉,没关系,今天的旅途累了,我们休息一下,下节继续,感谢关注,感谢阅读!!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

并发编程系列之重入锁VS读写锁 的相关文章

  • 并发编程系列之Exchanger

    前言 上面我们介绍了信号量 再来说说交换者 这个东西用的不是很多 所以一般也不被经常关注 但是我们还是最好了解下 下面我将从什么是Exchanger以及如何使用Exchanger两个方面谈谈这个用于线程间协调的工具类 什么是Exchange
  • Java 多线程事务回滚 ——多线程插入数据库时事务控制

    背景 日常项目中 经常会出现一个场景 同时批量插入数据库数据 由于逻辑复杂或者其它原因 我们无法使用sql进行批量插入 串行效率低 耗时长 为了提高效率 这个时候我们首先想到多线程并发插入 但是如何控制事务呢 直接上干货 实现效果 开启多条
  • 第十三章:QT多线程(QThread)

    回顾 第一章 Qt的概述 第二章 在Ubuntu编写第一个Qt程序 第三章 Qt的字符串和字符编码 第四章 Qt的信号和槽 第五章 Qt容器窗口 父窗口 第六章 面向对象的Qt编程 第七章 Qt设计师使用 designer 第八章 Qt创造
  • 一次线上的GC问题排查

    6 19号下午 线上系统出现了一次实时链路数据 不通畅的问题 业务方反应更新的增量数据没有流入到HA3搜索集群 登录机器后检查日志后发现 在周六晚上到周天下午 cr search merge 机器人schema统一 表增量数据猛增 初步估计
  • 面试官:说说CountDownLatch,CyclicBarrier,Semaphore的原理?

    CountDownLatch CountDownLatch适用于在多线程的场景需要等待所有子线程全部执行完毕之后再做操作的场景 举个例子 早上部门开会 有人在上厕所 这时候需要等待所有人从厕所回来之后才能开始会议 public class
  • 【2021最新版】Java多线程&并发面试题总结(108道题含答案解析)

    文章目录 JAVA并发知识库 1 Java中实现多线程有几种方法 2 继承Thread类 3 实现Runnable接口 4 ExecutorService Callable Future有返回值线程 5 基于线程池的方式 6 4 种线程池
  • 生产者与消费者问题?

    生产者消费者模式是并发 多线程编程中经典的设计模式 简单来看 就是一个类负责生产 一个类负责消费 举例来说 一个变量 生产者不断增加这个变量 消费者不断减少这个变量 在互联网应用中 抢票机制就是应用了该模式 比如大麦网演唱会门票抢票 123
  • 上下文切换理解以及减少方法

    并发编程面临着上下文切换 死锁等问题 尤其在少量数据的情况下 并发可能因为线程的创建和上下文切换的开销等问题 甚至比串行执行的速度更慢 文章目录 上下文切换定义 例子理解 减少上下文切换的方法 无锁并发编程 CAS算法 使用最少线程 协程
  • 如何设计高性能的分布式锁

    什么是分布式锁 在 JVM 中 在多线程并发的情况下 我们可以使用同步锁或 Lock 锁 保证在同一时间内 只能有一个线程修改共享变量或执行代码块 但现在我们的服务都是基于分布式集群来实现部署的 对于一些共享资源 在分布式环境下使用 Jav
  • JUC常用到的类

    JUC java util concurrent 并发包中包含了许多并发编程中需要用到的类 锁 如ReentratLock ReadWriteLock ReentrantLock重入锁 可以替代synchronized使用 并且有更多强大的
  • Java并发编程-第二章

    以下内容来自 Java并发编程 书籍第二章 补充 1 volatile的有序性 volatile通过内存屏障实现禁止指令重排序保证有序性 硬件层面的内存屏障分为Load Barrier 和 Store Barrier即读屏障和写屏障 2 同
  • BlockingQueue、ArrayBlockingQueue、LinkedBlockingQueue原理分析

    阻塞队列与非阻塞队 阻塞队列与普通队列的区别在于 当队列是空的时 从队列中获取元素的操作将会被阻塞 或者当队列是满时 往队列里添加元素的操作会被阻塞 试图从空的阻塞队列中获取元素的线程将会被阻塞 直到其他的线程往空的队列插入新的元素 同样
  • QT多线程基础

    文章目录 简介 相关名词 QT 运行方式 基础使用方法 void QObject moveToThread QThread targetThread 退出线程过程 wait 等待子线程的结束 实例 QT锁QMutex QMutexLocke
  • 高并发,你真的理解透彻了吗?

    高并发 几乎是每个程序员都想拥有的经验 原因很简单 随着流量变大 会遇到各种各样的技术问题 比如接口响应超时 CPU load升高 GC频繁 死锁 大数据量存储等等 这些问题能推动我们在技术深度上不断精进 在过往的面试中 如果候选人做过高并
  • 测试开发工程师面试总结(一)——Java基础篇

    本文面向对象 测试开发工程师 服务端自动化方向 随手百度一下都能找到 岗位面试总结 但是有关测开岗位的面试总结却寥寥无几 总体原因可能是这两个 1 测试行业整体水平参差不齐 导致不同公司面试的问题不能抽象出来写概览 2 很多做测开的人可能内
  • ThreadPoolExecutor源码解析

    ThreadPoolExecutor源码解析 一 新建线程池的是构造方法 public ThreadPoolExecutor int corePoolSize int maximumPoolSize long keepAliveTime T
  • 深入理解synchronized底层原理,一篇文章就够了!

    文章目录 前言 一 synchronized的特性 1 1 原子性 1 2 可见性 1 3 有序性 1 4 可重入性 二 synchronized的用法 三 synchronized锁的实现 3 1 同步方法 3 2 同步代码块 四 syn
  • brpc源码解析(十七)—— bthread上的类futex同步组件butex详解

    文章目录 一 futex简介 二 butex源码解析 2 1 butex相关数据结构 2 2 butex主要机制 2 2 1 butex wait 2 2 2 butex wake 我们知道在linux 下 锁和其他一些同步机制都会用到fu
  • 并发编程系列之自定义线程池

    前言 前面我们在讲并发工具类的时候 多次提到线程池 今天我们就来走进线程池的旅地 首先我们先不讲线程池框架Executors 我们今天先来介绍如何自己定义一个线程池 是不是已经迫不及待了 那么就让我们开启今天的旅途吧 什么是线程池 线程池可
  • Java线程(Thread)生命周期的6种状态

    当线程被创建并启动以后 它既不是一启动就进入了执行状态 也不是一直处于执行状态 在线程的生命周期中 可能处于不同的状态 java lang Thread State 列举出了这6种线程状态 线程状态 导致状态发生条件 New 新建 线程刚被

随机推荐

  • vue3之后台管理系统权限

    权限概括 后台管理系统中权限是不可少的一部分 例如 页面权限 菜单权限 按钮权限 路由权限等 文章目录 权限概括 定义权限 一 用户登录和认证 二 前端路由控制 三 菜单权限 四 数据级别的权限控制 五 前端界面的反馈 六 按钮权限 实现思
  • java+selenium3

    一 环境搭建 1 JDK安装 配置环境变量 2 selenium下载 3 测试Demo public static void main String args throws InterruptedException todo System
  • 华为机试—字符串处理专题

    文章目录 leetbook 字符串 125 验证回文串 5 最长回文子串 131 分割回文串 见回溯 HJ1计算字符串最后一个单词的长度 单词以空格隔开 HJ2输出输入字符串中含有该字符的个数 HJ4字符串分隔 连续输入多行字符串所以用ge
  • 硬件基础元器件【1.电阻篇】

    文章目录 1 电阻 1 1 电阻的作用 1 2 电阻选型要点 1 3 电阻的主要使用场景 1 3 1 上 下拉电阻 上下拉电阻作用 阻值选择原则 1 3 2 MOS管栅极驱动电阻 1 3 3 电源反馈电阻 1 3 4 晶振并联电阻 1 3
  • 算法(C++):加一

    算法 C 加一 题目难度 简单 题目描述 给定一个由 整数 组成的 非空 数组所表示的非负整数 在该数的基础上加一 最高位数字存放在数组的首位 数组中每个元素只存储单个数字 你可以假设除了整数 0 之外 这个整数不会以零开头 示例1 输入
  • centos7下docker中mysql大小写敏感相关问题解决方案

    因为linux下的mysql默认区分大小写 而windows下的mysql默认不区分 所以关联的时候 有时候会出问题 所以我们要取消linux小的区分大小写 目录 一 mysql大小写解决方案 1 启动docker 2 运行mysql容器
  • zabbix监控TCP连接状态

    一 zabbix监控TCP连接状态 1 取到TCP连接状态的值 root web01 netstat antp awk NR gt 2 print 6 grep TIME WAIT wc l 可以取到TIME WAIT的个数 依次类推 可以
  • linux安装mysql-8.0.11出现错误

    linux安装mysql 8 0 11出现 2020 04 16T11 47 06 723455Z 0 Warning MY 011070 Server Disabling symbolic links using skip symboli
  • java基础经典题——猴子吃桃

    作为学java循环的经典问题 猴子第一天摘了若干个桃子 当即吃了一半 还不解馋 又多吃了一个 第二天 吃剩下的桃子的一半 还不过瘾 又多吃了一个 以后每天都吃前一天剩下的一半多一个 到第10天想再吃时 只剩下一个桃子了 问第一天共摘了多少个
  • Ubuntu系统中如何删除一个用户

    1 打开终端命令行 运用userdel命令删除指定的用户 注意要加sudo权限指令 如下图 sudo userdel ascend 2 在删除的时候一定注意是在管理员目录下的 普通的用户是没有这个权限的 3 删除指令执行完了以后怎么判断是否
  • linux桌面小程序开发日记4(pyqt5+yolov5)

    linux桌面小程序开发日记4 修改detect py文件 让yolov5连接摄像头 同时输出识别出来的内容 最后一篇博客地址 https blog csdn net Liuchengzhizhi article details 12369
  • 论穷举法破解0到6位数登录密码的可行性

    0到6位数密码含数字 字母大小写 英文符号有537412247190种可能性 千亿数量级 3998 410GB 在局域网网速 个人台式电脑情况下 java代码 httpclient 访问路由器网址一次要414ms 如果只访问头信息的话会快1
  • ROI pooling 和 ROI Align详解

    ROI Align 是在Mask RCNN这篇论文里提出的一种区域特征聚集方式 很好地解决了ROI Pooling操作中两次量化造成的区域不匹配 mis alignment 的问题 实验显示 在检测测任务中将 ROI Pooling 替换为
  • 华为OD机试(Java,JS,Python,C++)-Excel单元格数值统计

    Excel单元格数值统计 时间限制 2s 空间限制 256MB 限定语言 不限 题目描述 Excel工作表中对选定区域的数值进行统计的功能非常实用 仿照Excel的这个功能 请对给定表格中选中区域中的单元格进行求和统计 并输出统计结果 为简
  • innodb事务实现

    事务的特性 ACID 事务的类别 事务实现 redo redoLog buffer 的格式 undo 更新主键 purge group commit 因为上层的binlog和底层的redolog要保持一致 所以 事务控制语句 事务隔离级别
  • C++11标准模板(STL)- 算法(std::rotate)

    定义于头文件
  • logistics回归之sklearn中的LogisticRegressionCV

    一 Logistic回归的认知与应用场景 Logistic回归为概率型非线性回归模型 是研究二分类观察结果与一些影响因素之间关系的 一种多变量分析方法 通常的问题是 研究某些因素条件下某个结果是否发生 比如医学中根据病人的一些症状 来判断它
  • Android-打包AAR步骤以及最为关键的注意事项

    转自 https www jianshu com p f391d0a6691e 简介 最近因为项目的要求 需要把开发的模块打包成aar 供其他项目调用 在搞了一段时间后 发现这里还是有很多需要注意的地方 所以记录一下 帮助大家不要走弯路 首
  • 调试for循环的技巧

    今天用IDEA调试for循环时 断点打在了for的左边 不小心按了F9 跳到下一个断点 意外发现原来可以通过这种方式对for循环的每次循环进行调试 不必自己手动一行行调试才到下一次for循环 这对于一种场景特别适用 假设你需要查看第 5 次
  • 并发编程系列之重入锁VS读写锁

    前言 上节我们介绍了Java中的锁基础篇 也算是对锁有了个基本的认识 对锁底层的一些原理有所掌握 那么今天我们就来看看2个最常见的锁的实例应用 重入锁和读写锁 这是今天旅途最美的两大景点 是不是有点迫不及待了 OK 那就让我们一起开启今天的