JUC面试(十三)——锁膨胀

news2024/9/23 1:36:22

锁膨胀

monitor概念

在这里插入图片描述

Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。上面这个图,描述了线程和 Monitor之间关系,以及线程的状态转换图。

进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则迚入拥有者;否则则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。

拥有者(The Owner):表示某一线程成功竞争到对象锁。

等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。

从图中可以看出,一个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”。 先看 “Entry Set”里面的线程。我们称被 synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。

synchronized实现原理

synchronized是依赖于JVM来实现同步的,在同步方法和代码块的原理有点区别。

同步代码块

我们在代码块加上synchronized关键字

public void synSay() {    
    synchronized (object) {        
        System.out.println("synSay----" + Thread.currentThread().getName());   
    }
}

编译之后,我们利用反编译命令javap -v xxx.class查看对应的字节码,这里为了减少篇幅,我就只粘贴对应的方法的字节码。

public void synSay();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=3, locals=3, args_size=1
       0: aload_0
       1: getfield      #2                  // Field object:Ljava/lang/String;
       4: dup
       5: astore_1
       6: monitorenter
       7: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: new           #4                  // class java/lang/StringBuilder
      13: dup
      14: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      17: ldc           #6                  // String synSay----
      19: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: invokestatic  #8                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      25: invokevirtual #9                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      34: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      37: aload_1
      38: monitorexit
      39: goto          47
      42: astore_2
      43: aload_1
      44: monitorexit
      45: aload_2
      46: athrow
      47: return
    Exception table:
       from    to  target type
           7    39    42   any
          42    45    42   any
    LineNumberTable:
      line 21: 0
      line 22: 7
      line 23: 37
      line 24: 47
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      48     0  this   Lcn/T1;
    StackMapTable: number_of_entries = 2
      frame_type = 255 /* full_frame */
        offset_delta = 42
        locals = [ class cn/T1, class java/lang/Object ]
        stack = [ class java/lang/Throwable ]
      frame_type = 250 /* chop */
        offset_delta = 4

可以发现synchronized同步代码块是通过加monitorentermonitorexit指令实现的。
每个对象都有个监视器锁(monitor) ,当monitor被占用的时候就代表对象处于锁定状态,而monitorenter指令的作用就是获取monitor的所有权,monitorexit的作用是释放monitor的所有权,这两者的工作流程如下:
monitorenter

  1. 如果monitor的进入数为0,则线程进入到monitor,然后将进入数设置为1,该线程称为monitor的所有者。
  2. 如果是线程已经拥有此monitor(即monitor进入数不为0),然后该线程又重新进入monitor,则将monitor的进入数+1,这个即为锁的重入
  3. 如果其他线程已经占用了monitor,则该线程进入到阻塞状态,知道monitor的进入数为0,该线程再去重新尝试获取monitor的所有权

monitorexit

执行该指令的线程必须是monitor的所有者,指令执行时,monitor进入数-1,如果-1后进入数为0,那么线程退出monitor,不再是这个monitor的所有者。这个时候其它阻塞的线程可以尝试获取monitor的所有权。

mointorenter对应2个monitorexit,一个正常退出exit,一个是出现异常的时候退出exit。

同步方法

在方法上加上synchronized关键字

synchronized public void synSay() {    
    System.out.println("synSay----" + Thread.currentThread().getName());
}

编译之后,我们利用反编译命令javap -v xxx.class查看对应的字节码,这里为了减少篇幅,我就只粘贴对应的方法的字节码。

public synchronized void synSay();
  descriptor: ()V
  flags: ACC_PUBLIC, ACC_SYNCHRONIZED
  Code:
    stack=3, locals=1, args_size=1
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #5                  // String synSay----
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: invokestatic  #7                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      18: invokevirtual #8                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      21: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      27: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      30: return
    LineNumberTable:
      line 20: 0
      line 21: 30
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      31     0  this   Lcn/T1;

从字节码上看,加有synchronized关键字的方法,常量池中比普通的方法多了个ACC_SYNCHRONIZED标识,JVM就是根据这个标识来实现方法的同步。
当调用方法的时候,调用指令会检查方法是否有ACC_SYNCHRONIZED标识,有的话线程需要先获取monitor,获取成功才能继续执行方法,方法执行完毕之后,线程再释放monitor,同一个monitor同一时刻只能被一个线程拥有。

两种同步方式区别

synchronized同步代码块的时候通过加入字节码monitorentermonitorexit指令来实现monitor的获取和释放,也就是需要JVM通过字节码显式的去获取和释放monitor实现同步,而synchronized同步方法的时候,没有使用这两个指令,而是检查方法的ACC_SYNCHRONIZED标志是否被设置,如果设置了则线程需要先去获取monitor,执行完毕了线程再释放monitor,也就是不需要JVM去显式的实现。
这两个同步方式实际都是通过获取monitor和释放monitor来实现同步的,而monitor的实现依赖于底层操作系统的mutex互斥原语,而操作系统实现线程之间的切换的时候需要从用户态转到内核态,这个转成过程开销比较大。
线程获取、释放monitor的过程如下:

在这里插入图片描述

线程尝试获取monitor的所有权,如果获取失败说明monitor被其他线程占用,则将线程加入到的同步队列中,等待其他线程释放monitor当其他线程释放monitor后,有可能刚好有线程来获取monitor的所有权,那么系统会将monitor的所有权给这个线程,而不会去唤醒同步队列的第一个节点去获取,所以synchronized是非公平锁。如果线程获取monitor成功则进入到monitor中,并且将其进入数+1

到这里我们也清楚了synchronized的语义底层是通过一个monitor的对象完成,其实waitnotiyfnotifyAll等方法也是依赖于monitor对象来完成的,这也就是为什么需要在同步方法或者同步代码块中调用的原因(需要先获取对象的锁,才能执行),否则会抛出java.lang.IllegalMonitorStateException的异常

Java对象的组成

我们知道了线程要访问同步方法、代码块的时候,首先需要取得锁,在退出或者抛出异常的时候又必须释放锁,那么锁到底是什么?又储存在哪里?
为了解开这个疑问,我们需要进入Java虚拟机(JVM) 的世界。在HotSpot虚拟机中,Java对象在内存中储存的布局可以分为3块区域:对象头实例数据对齐填充synchronized使用的锁对象储存在对象头中

在这里插入图片描述

在这里插入图片描述

对象头

对象头的数据长度在32位和64位(未开启压缩指针)的虚拟机中分别为32bit64bit。对象头由以下三个部分组成:

  • Mark Word:记录了对象和锁的有关信息,储存对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁标志位、线程持有的锁、偏向线程ID、偏向时间戳、对象分代年龄等。注意这个Mark Word结构并不是固定的,它会随着锁状态标志的变化而变化,而且里面的数据也会随着锁状态标志的变化而变化,这样做的目的是为了节省空间
  • 类型指针:指向对象的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  • 数组长度:这个属性只有数组对象才有,储存着数组对象的长度。

32位虚拟机下,Mark Word的结构和数据可能为以下5种中的一种。

64位虚拟机下,Mark Word的结构和数据可能为以下2种中的一种。

这里重点注意是否偏向锁锁标志位,这两个标识和synchronized的锁膨胀息息相关。

实例数据

储存着对象的实际数据,也就是我们在程序中定义的各种类型的字段内容。

对齐填充

HotSpot虚拟机的对齐方式为8字节对齐,即一个对象必须为8字节的整数倍,如果不是,则通过这个对齐填充来占位填充。

synchronized锁膨胀过程

从JDK1.6版本之后,synchronized本身也在不断优化锁的机制,有些情况下他并不会是⼀个很重量级的锁。优化机制包括⾃适应锁、⾃旋锁、锁消除、锁粗化、偏向锁、轻量级锁。

上文介绍的 “synchronized实现原理” 实际是synchronized实现重量级锁的原理,那么上文频繁提到monitor对象和对象又存在什么关系呢,或者说monitor对象储存在对象的哪个地方呢?
在对象的对象头中,当锁的状态为重量级锁的时候,它的指针即指向monitor对象,如图:

在这里插入图片描述

那锁的状态为其它状态的时候是不是就没用上monitor对象?答案:是的。
这也是JVMsynchronized的优化,我们知道重量级锁的实现是基于底层操作系统的mutex(mi de s)互斥原语的,这个开销是很大的。所以JVMsynchronized做了优化,JVM先利用对象头实现锁的功能,如果线程的竞争过大则会将锁升级(膨胀)为重量级锁,也就是使用monitor对象。当然JVM对锁的优化不仅仅只有这个,还有引入适应性自旋、锁消除、锁粗化、轻量级锁、偏向锁等。

那么锁的是怎么进行膨胀的或者依据什么来膨胀,这也就是本篇需要介绍的重点,首先我们需要了解几个概念。

锁的优化

自旋锁和自适应性自旋锁

自旋:当有个线程A去请求某个锁的时候,这个锁正在被其它线程占用,但是线程A并不会马上进入阻塞状态,而是循环请求锁(自旋)。这样做的目的是因为很多时候持有锁的线程会很快释放锁的,线程A可以尝试一直请求锁,没必要被挂起放弃CPU时间片,因为线程被挂起然后到唤醒这个过程开销很大,当然如果线程A自旋指定的时间还没有获得锁,仍然会被挂起。

自适应性自旋:自适应性自旋是自旋的升级、优化,自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定。例如线程如果自旋成功了,那么下次自旋的次数会增多,因为JVM认为既然上次成功了,那么这次自旋也很有可能成功,那么它会允许自旋的次数更多。反之,如果对于某个锁,自旋很少成功,那么在以后获取这个锁的时候,自旋的次数会变少甚至忽略,避免浪费处理器资源。有了自适应性自旋,随着程序运行和性能监控信息的不断完善,JVM对程序锁的状况预测就会变得越来越准确,JVM也就变得越来越聪明。

锁消除

锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除

锁粗化

在使用锁的时候,需要让同步块的作用范围尽可能小,这样做的目的是为了使需要同步的操作数量尽可能小,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁

轻量级锁

所谓轻量级锁是相对于使用底层操作系统mutex互斥原语实现同步的重量级锁而言的,因为轻量级锁同步的实现是基于对象头的Mark Word。那么轻量级锁是怎么使用对象头来实现同步的呢,我们看看具体实现过程。

获取锁过程

  1. 在线程进入同步方法、同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为”01”状态,是否为偏向锁为”0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Recored)的空间,用于储存锁对象目前的Mark Word的拷贝(官方把这份拷贝加了个Displaced前缀,即Displaced Mark Word)。

在这里插入图片描述

  1. 将对象头的Mark Word拷贝到线程的锁记录(Lock Recored)中。
  2. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。如果这个更新成功了,则执行步骤4,否则执行步骤5
  3. 更新成功,这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位将转变为”00”,即表示此对象处于轻量级锁的状态。
  4. 更新失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,可以直接进入同步块继续执行,否则说明这个锁对象已经被其其它线程抢占了。进行自旋执行步骤3,如果自旋结束仍然没有获得锁,轻量级锁就需要膨胀为重量级锁,锁标志位状态值变为”10”,Mark Word中储存就是指向monitor对象的指针,当前线程以及后面等待锁的线程也要进入阻塞状态。

释放锁的过程

  1. 使用CAS操作将对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来(依据Mark Word中锁记录指针是否还指向本线程的锁记录),如果替换成功,则执行步骤2,否则执行步骤3
  2. 如果替换成功,整个同步过程就完成了,恢复到无锁的状态(01)。
  3. 如果替换失败,说明有其他线程尝试获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。

偏向锁

偏向锁的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作区消除同步使用的互斥量,那么偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不用做了。偏向锁默认是开启的,也可以关闭。可以⽤过设置-XX:+UseBiasedLocking开启偏向锁。
偏向锁”偏”,就是”偏心”的”偏”,它的意思是这个锁会偏向于第一个获得它的程序,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。

获取锁的过程

  1. 检查Mark Word是否为可偏向锁的状态,即是否偏向锁即为1即表示支持可偏向锁,否则为0表示不支持可偏向锁。
  2. 如果是可偏向锁,则检查Mark Word储存的线程ID是否为当前线程ID,如果是则执行同步块,否则执行步骤3
  3. 如果检查到Mark WordID不是本线程的ID,则通过CAS操作去修改线程ID修改成本线程的ID,如果修改成功则执行同步代码块,否则执行步骤4
  4. 当拥有该锁的线程到达安全点之后,挂起这个线程,升级为轻量级锁。

锁释放的过程

  1. 有其他线程来获取这个锁,偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争。
  2. 等待全局安全点(在这个是时间点上没有字节码正在执行)。
  3. 暂停拥有偏向锁的线程,检查持有偏向锁的线程是否活着,如果不处于活动状态,则将对象头设置为无锁状态,否则设置为被锁定状态。如果锁对象处于无锁状态,则恢复到无锁状态(01),以允许其他线程竞争,如果锁对象处于锁定状态,则挂起持有偏向锁的线程,并将对象头Mark Word的锁记录指针改成当前线程的锁记录,锁升级为轻量级锁状态(00)

在这里插入图片描述

锁的转换过程

锁主要存在4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争的情况逐渐升级,这几个锁只有重量级锁是需要使用操作系统底层mutex互斥原语来实现,其他的锁都是使用对象头来实现的。需要注意锁可以升级,但是不可以降级。

在这里插入图片描述

锁膨胀

首先简单说下偏向锁、轻量级锁、重量级锁三者各自的应用场景:

  • 偏向锁:只有一个线程进入临界区;
  • 轻量级锁:多个线程交替进入临界区**;**
  • 重量级锁:多个线程同时进入临界区。

锁膨胀过程:

在这里插入图片描述

上图跟下面这个解析基本相同:

偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。

一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。

一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,( 偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。

轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/181880.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows11 永久关闭windows defender的方法

1、按键盘上的windows按键&#xff0c;再点【设置】选项。 2、点击左侧菜单的【隐私和安全性】&#xff0c;再点击列表的【Windows安全中心】选项。 3、点击界面的【病毒和威胁保护】设置项。 4、病毒保护的全部关闭 5、别人的图&#xff08;正常是都开着的&#xff09; 6、终极…

为什么看上去很简单的智慧功能点要价上千万?

人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;已经不是什么新概念&#xff0c;第三次浪潮于2016年AlphaGo战胜李世石为标志正式开启&#xff0c;至今也已经走过6个年头。 发展至今&#xff0c;AI已经进入老百姓的日常生活&#xff0c;比如随处可见的…

【C语言】从0到1带你学会文件版动态通讯录

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;别人可以拷贝我的模式&#xff0c;但不能拷贝我不断往前的激情 &#x1f6f8;C语言专栏&#xff1a;https://blog.csdn.net/vhhhbb/category_12174730.html 小苏希望大家能从这篇文章中收获到许…

初学者试试,HarmonyOS应用开发者基础认证

一些初学HarmonyOS应用开发的同学往往不知道如何开始&#xff0c;建议先试试《HarmonyOS应用开发者基础认证》&#xff0c;基础认证是华为进一步大范围布局推广“鸿蒙世界”的新举措。也是初学者开启鸿蒙世界的一把钥匙。 【说说鸿蒙世界】 相信大家已经对鸿蒙不陌生了&#x…

IDEA新建js项目和执行js脚本

一)、安装Node.js具体操作参考:https://blog.csdn.net/xijinno1/article/details/128774375二)、IDEA中新建js项目(hello world)1.按照下图&#xff0c;新建js项目2.选中示例代码文件后点击运行->运行3.选择【编辑配置】4.更新一下节点解释器(nodejs.exe)&#xff0c;点击运…

界面组件DevExpress WPF v22.2 - Windows 11暗黑主题发布

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

Unity 之 Addressable可寻址系统 -- 资源远程加载 | 资源预下载

可寻址系统远程加载 -- 资源预下载 -- 进阶&#xff08;三&#xff09;一&#xff0c;Unity 云资源分发 -- 使用介绍1.1 CCD 的介绍1.2 后台准备工作二&#xff0c;CDD的使用2.1 CCD可视化界面的使用2.2 CDD命令行界面使用2.2.1 准备工作2.2.2 CLI 用法三&#xff0c;AA CCD资…

Qt扫盲-QObject对象和线程

QObject对象和线程一、概述二、QObjectReentrant性三、每个线程事件的循环四、从其他线程访问QObject的子类五、跨线程的信号和槽函数一、概述 QThread继承QObject。QThread它发出信号来指示线程开始或结束执行&#xff0c;还提供了一些任务槽。 Qobject可以在多个线程中使用…

L1-006 连续因子

一个正整数 N 的因子中可能存在若干连续的数字。例如 630 可以分解为 3567&#xff0c;其中 5、6、7 就是 3 个连续的数字。给定任一正整数 N&#xff0c;要求编写程序求出最长连续因子的个数&#xff0c;并输出最小的连续因子序列。 输入格式&#xff1a; 输入在一行中给出一…

Python爬虫(1)一次性搞定Selenium(新版)8种find_element元素定位方式

selenium中有8种不错的元素定位方式&#xff0c;每个方式和应用场景都不一样&#xff0c;需要根据自己的使用情况来进行修改 这里写目录标题1.id定位2.CSS定位3.XPATH定位4.name定位5.class_name定位6.Link_Text定位7.PARTIAL_LINK_TEXT定位8.TAG_NAME定位总结目前selenium已经…

力扣SQL刷题

目录标题571. 给定数字的频率查询中位数574. 当选者608. 树节点612. 平面上的最近距离619. 只出现一次的最大数字571. 给定数字的频率查询中位数 题型&#xff1a;给出数值和对应频率&#xff0c;返回中位数 解答&#xff1a;分别升序和降序排列。中位数正序和倒序所处的位置都…

人工智能图片素材高清,机器人图片卡通 素材

1、做一个人工智能的ppt需要哪些素材 不能理解你的问题。ppt仅仅是一个做幻灯的软件&#xff0c;可以插入图片、动画、音乐、过场等幻灯的功能&#xff0c;还谈不上有人工智能的能力。人工智能需要有更为强大的类似人脑思维的能力&#xff0c;简单的说人工智能是可以思考的机器…

【HBase——陌陌海量存储案例】2. HBase表结构设计(中)

前言 本文是陌陌海量存储案例——HBase表结构设计&#xff08;中&#xff09;&#xff0c;介绍ROWKEY设计原则、项目初始化。 4.5 ROWKEY设计原则 4.5.1 HBase官方的设计原则 避免使用递增行键/时序数据 如果ROWKEY设计的都是按照顺序递增&#xff08;例如&#xff1a;时间…

信息论复习—差错控制编码

目录 差错控制编码的基本概念&#xff1a; 差错控制编码的主要类型和方式&#xff1a; 差错控制编码的主要类型&#xff1a; 差错控制方式&#xff1a; 简单的差错控制方法&#xff1a; 奇偶校验码&#xff1a; 重复码&#xff1a; 信道编码的基本概念和定理*: 信道编…

4 反向传播

文章目录问题提出计算图中的神经网络改进&#xff08;激活函数&#xff09;反向传播前馈计算反向传播pytorch当中的前馈与反馈Tensor张量课程代码课后作业课程来源&#xff1a; 链接课程内容部分来源&#xff08;觉得归纳的非常好的&#xff09;&#xff1a; 链接以及&#xff…

MySQL中的运算符

目录 一.运算符 简介 算术运算符 比较运算法 逻辑运算符 位运算符 实例演示 一.运算符 简介 数据库中的表结构确立后&#xff0c;表中的数据代表的意义就已经确定。通过MySQL运算符进行运算&#xff0c;就可以获取到表结构以外的另一种数据。 例如&#xff0c;学生表中…

[数据结构笔记]二叉树初阶

基本知识 树 -节点的度&#xff1a;一个节点含有的子树的个数称为该节点的度&#xff1b; -叶节点或终端节点&#xff1a;度为0的节点称为叶节点&#xff1b; -非终端节点或分支节点&#xff1a;度不为0的节点&#xff1b; -父节点&#xff1a;若一个节点含有子节点&#xff…

Mybatis 通过接口实现 sql 执行原理解析

使用过 mybatis 框架的小伙伴们都知道&#xff0c;mybatis 是个半 orm 框架&#xff0c;通过写 mapper 接口就能自动实现数据库的增删改查&#xff0c;但是对其中的原理一知半解&#xff0c;接下来就让我们深入框架的底层一探究竟1、环境搭建首先引入 mybatis 的依赖&#xff0…

Consul服务注册与发现

目录 一、Consul简介 &#xff08;一&#xff09;官网 &#xff08;二&#xff09;特点 二、安装并运行Consul &#xff08;一&#xff09;官网安装说明 &#xff08;二&#xff09;下载 &#xff08;三&#xff09;使用开发模式启动 三、服务提供者 四、服务消费者 …

怎么压缩pdf文件?选对方法其实很简单!

相信许多人在使用设备的时候都会面对这样一个问题&#xff0c;那就是设备内存不足。仿佛不管我们多么努力的节省空间&#xff0c;总是会到头来遇到储存空间不足得难题&#xff0c;尤其是一些比较大的pdf文件&#xff0c;特别占据我们的设备内存&#xff0c;那么你知道怎么压缩p…