Java中锁的全面详解(深刻理解各种锁)

一.Monitor

1. Java对象头

以32位虚拟机位例

对于普通对象,其对象头的存储结构为

总长为64位,也就是8个字节, 存在两个部分

  • Kclass Word: 其实也就是表示我们这个对象属于什么类型,也就是哪个类的对象.
  • 而对于Mark Word.查看一下它的结构存储

64位虚拟机中

而对于数组对象,我们将会多出32位来存储数组的长度.

2. Monitor原理

Monitor也就是管程,监视器,其实也就是我们熟知的锁.

每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针.(只有重量级锁才会关联Monitor)

也就是如下图所示:

  • 每个对象都拥有一把锁,假设此时锁对象为Object.class对象.此时Thread-2拿到锁,会将自己的Mark Word指向对应的Monitor.也就是像这样(JDK6以前为重量级锁).此时会将自己原有的,例如之前的年龄age,hashcode等存储到Monitor中.

  • 其他线程来抢占锁,突然发现锁的Monitor中的Owner属性不为null.则进入阻塞状态.
  • 等Thread-2中代码执行完毕,将锁释放.Monitor将会唤醒所有阻塞的线程,并把之前存储的原有Object.class中对象头中的属性重新赋值给Object.class.此时其他线程就可以来抢占锁.(不公平的抢占)

注意:

  1. synchronized 必须是进入同一个对象的 monitor 才有上述的效果
  2. 不加 synchronized 的对象不会关联监视器,不遵从以上规则

字节码角度分析:

对于这样一段代码

我们查看对应的字节码指令,以来理解这个过程.

以上便是sybchronized过去的原理.

二.synchronized优化原理

而在从JDK6开始,HotSpot虚拟机开发团队对Java中的锁进行优化,

如增加了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略,提升了 synchronized的性能.

1. 轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以 使用轻量级锁来优化。

创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word

让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录

如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下

如果 cas 失败,有两种情况

  1. 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
  2. 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一

当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头

  • 成功,则解锁成功
  • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

总的来说就是:

1. 进行加锁操作时,jvm会判断是否已经是重量级锁,如果不是,则会在当前线程栈帧中划出一块空间,作为该锁的锁记录,并且将锁对象MarkWord复制到该锁记录中

2. 复制成功之后,jvm使用CAS操作将对象头MarkWord更新为指向锁记录的指针,并将锁记录里的owner指针指向对象头的MarkWord。如果成功,则执行‘3’,否则执行‘4’

3. 更新成功,则当前线程持有该对象锁,并且对象MarkWord锁标志设置为‘00’,即表示此对象处于轻量级锁状态

4. 更新失败,jvm先检查对象MarkWord是否指向当前线程栈帧中的锁记录,如果是则执行‘5’,否则执行‘6’

5. 表示锁重入;然后当前线程栈帧中增加一个锁记录第一部分(Displaced Mark Word)为null,并指向Mark Word的锁对象,起到一个重入计数器的作用。

6. 表示该锁对象已经被其他线程抢占,则进行自旋等待(默认10次),等待次数达到阈值仍未获取到锁,则升级为重量级锁(会由另一个线程为锁对象申请Monitor,也就是接下来的锁膨胀)

2.可重入锁

synchronized可重入的实现原理(优化过后)

  1. 锁对象与计数器
    • 每个Java对象都有一个与之关联的监视器(Monitor)。这个监视器内部维护了一个计数器(通常称为recursions变量),用于记录当前线程获取该对象锁的次数。
    • 当线程首次进入synchronized修饰的代码块或方法时,会尝试获取对象的锁。如果锁未被占用(即计数器为0),则线程获取锁并将计数器设置为1。(与之前再创造一个Lock Record不同)
  1. 重入机制
    • 如果当前线程已经持有了该对象的锁(即该线程已经执行了synchronized修饰的代码块或方法),当再次尝试进入另一个由相同对象锁保护的synchronized代码块或方法时,JVM会检查持有锁的线程是否为当前线程。
    • 如果是,则JVM允许该线程再次获取锁,并将计数器的值加1,而不是让线程等待锁被释放。这个过程就是synchronized的可重入性。
  1. 锁的释放
    • 当线程退出synchronized修饰的代码块或方法时,JVM会自动将对象锁的计数器减1
    • 如果计数器减至0,则表示当前线程已经释放了该对象的锁,其他等待获取该锁的线程将有机会获取锁并进入相应的synchronized代码块或方法。

2. 锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁.

static Object obj = new Object();
public static void method1() {synchronized( obj ) {// 同步块}
}
  • 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

这时 Thread-1 加轻量级锁失败,进入锁膨胀流程

  • 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
  • 然后自己进入 Monitor 的 EntryList BLOCKED

  • 当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

3. 自旋锁

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

自旋重试成功的情况

自旋重试失败的情况

注意:

  1. 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  2. 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  3. Java 7 之后不能控制是否开启自旋功能

4. 偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有.可以理解为,偏向锁就是JVM认为这把锁就是属于一个线程

static final Object obj = new Object();
public static void m1() {synchronized( obj ) {// 同步块 Am2();}
}
public static void m2() {synchronized( obj ) {// 同步块 Bm3();}
}
public static void m3() {synchronized( obj ) {}
}

4.1 偏向状态

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后3位,为101这时它的thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数

-XX:BiasedLockingStartupDelay=0 来禁用延迟

  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、 age 都为 0,第一次用到 hashcode 时才会赋值

// 添加虚拟机参数 -XX:BiasedLockingStartupDelay=0 
public static void main(String[] args) throws IOException {Dog d = new Dog();ClassLayout classLayout = ClassLayout.parseInstance(d);new Thread(() -> {log.debug("synchronized 前");System.out.println(classLayout.toPrintableSimple(true));synchronized (d) {log.debug("synchronized 中");System.out.println(classLayout.toPrintableSimple(true));}log.debug("synchronized 后");System.out.println(classLayout.toPrintableSimple(true));}, "t1").start();
}

输出

11:08:58.117 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101

注意:

处于偏向锁的对象解锁后,线程 id 仍存储于对象头中

测试禁用

在上面测试代码运行时在添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁

输出

11:13:10.018 c.TestBiased [t1] - synchronized 前 
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
11:13:10.021 c.TestBiased [t1] - synchronized 中 
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000 
11:13:10.021 c.TestBiased [t1] - synchronized 后 
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
4.2 撤销 - 调用对象 hashCode

调用了对象的 hashCode, 但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

所以由于偏向锁没有存储空间来存储hashCode的值,只能撤销.(仔细查看偏量锁的对象头结构)

  • 在调用 hashCode 后使用偏向锁,记得去掉 -XX:-UseBiasedLocking(禁用默认偏向锁.)

输出

11:22:10.386 c.TestBiased [main] - 调用 hashCode:1778535015 
11:22:10.391 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001 
11:22:10.393 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000 
11:22:10.393 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
4.3 撤销 - 其它线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

private static void test2() throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}synchronized (TestBiased.class) {TestBiased.class.notify();}// 如果不用 wait/notify 使用 join 必须打开下面的注释// 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的/*try {System.in.read();} catch (IOException e) {e.printStackTrace();}*/}, "t1");t1.start();Thread t2 = new Thread(() -> {synchronized (TestBiased.class) {try {TestBiased.class.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}, "t2");t2.start();
}

输出

[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000 
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
4.4 撤销 - 调用 wait/notify
public static void main(String[] args) throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));try {d.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t1");t1.start();new Thread(() -> {try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (d) {log.debug("notify");d.notify();}}, "t2").start();
}

输出

[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101 
[t2] - notify 
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010

4.5 批量重偏向

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的Thread ID .

当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得有可能偏向错了,于是会在给这些对象加锁时重新偏向至加锁线程.

private static void test3() throws InterruptedException {Vector<Dog> list = new Vector<>();Thread t1 = new Thread(() -> {for (int i = 0; i < 30; i++) {Dog d = new Dog();list.add(d);synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}synchronized (list) {list.notify();} }, "t1");t1.start();Thread t2 = new Thread(() -> {synchronized (list) {try {list.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("===============> ");for (int i = 0; i < 30; i++) {Dog d = list.get(i);log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t2");t2.start();
}

输出(只取对应了的部分)

[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101[t2] - ===============> (注意到19次的变化,没有撤销)
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
4.6 批量撤销

当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的.(第二个循环擦除了20次,第三个循环擦除了20次.这两个循环前20次都是擦除,然后锁重偏向,后19次都在锁偏向,即不会擦除)

private static void test4() throws InterruptedException {Vector<Dog> list = new Vector<>();int loopNumber = 39;t1 = new Thread(() -> {for (int i = 0; i < loopNumber; i++) {Dog d = new Dog();list.add(d);synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}LockSupport.unpark(t2);}, "t1");t1.start();t2 = new Thread(() -> {LockSupport.park();log.debug("===============> ");for (int i = 0; i < loopNumber; i++) {Dog d = list.get(i);log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}LockSupport.unpark(t3);}, "t2");t2.start();t3 = new Thread(() -> {LockSupport.park();log.debug("===============> ");for (int i = 0; i < loopNumber; i++) {Dog d = list.get(i);log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t3");t3.start();t3.join();log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}

5. 锁消除

锁消除,Java虚拟机中的即时编译器会自动帮我们优化,不存在锁共享的情况会直接将锁擦除.

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

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

相关文章

Java中的迭代器(Iterator)

Java中的迭代器&#xff08;Iterator&#xff09; 1、 迭代器的基本方法2、 迭代器的使用示例3、注意事项4、克隆与序列化5、结论 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java中&#xff0c;迭代器&#xff08;Iterator&#xff0…

Web开发:四角线框效果(HTML、CSS、JavaScript)

目录 一、实现效果 二、完整代码 三、页面准备 1、页面结构 2、初始样式 3、现有效果 三、线框实现 1、需求分析 2、线框结构 3、线框大小 4、线框位置 5、线框样式 6、移动线框 7、添加过渡效果 8、使用CSS变量 一、实现效果 如下图所示&#xff0c;当鼠标移动…

html 单页面引用vue3和element-plus

引入方式&#xff1a; element-plus基于vue3.0&#xff0c;所以必须导入vue3.0的js文件&#xff0c;然后再导入element-plus自身所需的js以及css文件&#xff0c;导入文件有两种方法&#xff1a;外部引用、下载本地使用 通过外部引用ElementPlus的css和js文件 以及Vue3.0文件 …

光热熔盐储能

长时储能的新赛道上&#xff0c;多种技术正在加速竞逐&#xff0c;谁最有可能成为其中的王者&#xff1f; 液流电池、压缩空气储能、重力储能&#xff1f;储能行业的玩家们通常不会想到的答案是光热熔盐储能。 1 基础的原理 光热发电系统包括太阳能集热、传储热、发电三大模…

MK米客方德推出新一代工业级SD NAND

--更长寿命、更高速度、更优功耗 目录 --更长寿命、更高速度、更优功耗 1.LGA-8封装&#xff1a; 2.工业级SLC存储颗粒&#xff1a; 3.高IOPS性能&#xff1a; 4.健康状态侦测(Smart Function)&#xff1a; 5.内嵌ECC校验、坏块管理、垃圾回收、磨损平均算法等功能。 6…

大厂面试官问我:Redis为什么使用哈希槽的方式进行数据分片?为什么不适用一致性哈希的方式?【后端八股文十三:Redis 集群哈希八股文合集(1)】

本文为【Redis 集群哈希 八股文合集&#xff08;1&#xff09;】初版&#xff0c;后续还会进行优化更新&#xff0c;欢迎大家关注交流~ hello hello~ &#xff0c;这里是绝命Coding——老白~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注…

百日筑基第二十三天-23种设计模式-创建型总汇

百日筑基第二十三天-23种设计模式-创建型总汇 前言 设计模式可以说是对于七大设计原则的实现。 总体来说设计模式分为三大类&#xff1a; 创建型模式&#xff0c;共五种&#xff1a;单例模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式。结构型模式&#xff0c;共…

防洪墙的安全内容检测+http请求头

1、华为的IAE引擎&#xff1a;内部工作过程 IAE引擎主要是针对2-7层进行一个数据内容的检测 --1、深度检测技术 (DPI和DPF是所有内容检测都必须要用到的技术) ---1、DPI--深度包检测&#xff0c;针对完整的数据包&#xff0c;进行内容的识别和检测 1、基于特征子的检…

数据分析01——系统认识数据分析

1.数据分析的全貌 1.1观测 1.1.1 观察 &#xff08;1&#xff09;采集数据 a.采集数据&#xff1a;解析系统日志 当你在看视频的时候———就会产生日志———解析日志———得到数据 b.采集数据&#xff1a;埋点获取新数据&#xff08;自定义记录新的信息&#xff09; 日志…

【Vue】Vue3 安装 Tailwind CSS 入门

初始化 Vue 3 项目 npm install -g vue/cli vue create my-project安装 Tailwind CSS 进入你的项目目录&#xff0c;然后安装 Tailwind CSS 和其依赖项&#xff1a; npm install -D tailwindcss postcss autoprefixer配置 PostCSS Tailwind CSS 需要通过 PostCSS 进行处理。…

Python酷库之旅-第三方库Pandas(029)

目录 一、用法精讲 74、pandas.api.interchange.from_dataframe函数 74-1、语法 74-2、参数 74-3、功能 74-4、返回值 74-5、说明 74-6、用法 74-6-1、数据准备 74-6-2、代码示例 74-6-3、结果输出 75、pandas.Series类 75-1、语法 75-2、参数 75-3、功能 75-4…

牛客 7.13 月赛(留 C逆元 Ddp)

B-最少剩几个&#xff1f;_牛客小白月赛98 (nowcoder.com) 思路 奇数偶数 奇数&#xff1b;奇数*偶数 奇数 所以在既有奇数又有偶数时&#xff0c;两者结合可以同时删除 先分别统计奇数&#xff0c;偶数个数 若偶个数大于奇个数&#xff0c;答案是偶个数-奇个数 若奇个数…

六边形动态特效404单页HTML源码

源码介绍 动态悬浮的六边形,旁边404文字以及跳转按钮,整体看着像科技二次元画风,页面简约美观,可以做网站错误页或者丢失页面,将下面的代码放到空白的HTML里面,然后上传到服务器里面,设置好重定向即可 效果预览 完整源码 <!DOCTYPE html> <html><head…

PyCharm查看文件或代码变更记录

背景&#xff1a; Mac笔记本上有一个截图的定时任务在运行&#xff0c;本地Python使用的是PyCharm IDE&#xff0c;负责的同事休假&#xff0c;然后定时任务运行的结果不符合预期&#xff0c;一下子不知道问题出现在哪里。 定位思路&#xff1a; 1、先确认网络、账号等基本的…

Qt 快速保存配置的方法

Qt 快速保存配置的方法 一、概述二、代码1. QFileHelper.cpp2. QSettingHelper.cpp 三、使用 一、概述 这里分享一下&#xff0c;Qt界面开发时&#xff0c;快速保存界面上一些参数配置的方法。 因为我在做实验的时候&#xff0c;界面上可能涉及到很多参数的配置&#xff0c;我…

实战打靶集锦-31-monitoring

文章目录 1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查4.1 ssh服务4.2 smtp服务4.3 http/https服务 5. 系统提权5.1 枚举系统信息5.2 枚举passwd文件5.3 枚举定时任务5.4 linpeas提权 6. 获取flag 靶机地址&#xff1a;https://download.vulnhub.com/monitoring/Monitoring.o…

django学习入门系列之第四点《BootStrap依赖》

文章目录 往期回顾 BootStrap依赖于JavaScript的类库&#xff0c;JQuery下载 下载JQuery&#xff0c;在界面上应用JQuery 在页面上应用BootStrap和avaScript的类库【JQuery是avaScript的类库】 JQuery的官网&#xff1a; jQuery 如果要应用JQuery 则要在body里面导入文件…

MySQL(7)内外连接+索引

目录 1.内外连接; 2. 索引; 1.内外连接: 1.1内连接: 语法: select 字段 from 表名 inner join 表名 on 字段限制; 1.2 外连接: 分为左右外连接; (1)左外连接: 语法: select * from 表名 left join 表名 on 字段限制. &#x1f330;查询所有学生的成绩&#xff0c;如果这个学生…

什么才是好用的大模型?

随着大模型在千行百业的逐步落地&#xff0c;中国基础大模型正直面来自用户端的诸多拷问。 近日&#xff0c;随着OpenAI宣布禁止中国用户使用其API&#xff0c;更多的国产大模型都在提供替代方案和优惠措施&#xff0c;来吸引和支持开发者进行用户迁移。 这既是一次挑战&…

多多OJ评测系统 前端项目环境初始化 安装Vue脚手架 引入Arco Design组件

目录 确定环境 命令行输入 装一下脚手架 监测一下是否安装成功 创建一个项目 选择一系列的配置后 我们打开webStorm 配置脚手架后我们先运行 我们这边能获取到网址 其实我们脚手架已经帮我们做到了 接下来要引入相关的组件 选择用npm进行安装 我们建议的是完整引入…