Java共享问题 、synchronized 线程安全分析、Monitor、wait/notify

文章目录

  • 1.共享带来的问题
    • 1.1 临界区 Critical Section
    • 1.2 竞态条件 Race Condition
  • 2. synchronized语法及理解
    • 2.1 方法上的 synchronized
  • 3.变量的线程安全分析
    • 3.1.成员变量和静态变量是否线程安全?
    • 3.2.局部变量是否线程安全?
      • 3.2.1 局部变量线程安全分析
  • 4.Monitor
    • 4.1 Java 对象头
    • 4.2 Monitor概念
  • 5.synchronized原理
    • 5.1 轻量级锁
    • 5.2 锁膨胀
    • 5.3 自旋优化
    • 5.4 偏向锁
      • 5.4.1 偏向状态
      • 5.4.2 撤销偏向锁
        • 5.4.2.1 撤销-调用对象hashCode
        • 5.4.2.3 撤销-其他线程使用对象
        • 5.4.2.4 撤销- 调用wait/notify
      • 5.4.3 批量重偏向
      • 5.4.4 批量撤销
      • 5.4.5 锁消除
      • 5.4.6 锁粗化
  • 6.wait/notify
    • 6.1 wait/notify原理
    • 6.2 API介绍
    • 6.3 wait、notify 的正确使用

1.共享带来的问题

(1)两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?

static int counter = 0;
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter++;}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter--;}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}",counter);
}

(2)以上的结果可能是正数、负数、零。为什么呢?因为 Java 中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析
例如:
①对于 i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:

getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatic i // 将修改后的值存入静态变量i

②而对应 i-- 也是类似:

getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
isub // 自减
putstatic i // 将修改后的值存入静态变量i

(3)如果是单线程以上 8 行代码是顺序执行(不会交错)没有问题:

在这里插入图片描述

(4)出现负数的情况
在这里插入图片描述

(5)出现正数的情况:

在这里插入图片描述

1.1 临界区 Critical Section

(1)一个程序运行多个线程本身是没有问题的
(2)问题出在多个线程访问共享资源
①多个线程读共享资源其实也没有问题
②在多个线程对共享资源读写操作时发生指令交错,就会出现问题
(3)一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
(4)例如,下面代码中的临界区

static int counter = 0;
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++)// 临界区{counter++;}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++)// 临界区{counter--;}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}",counter);
}

1.2 竞态条件 Race Condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。为了避免临界区的竞态条件发生,有多种手段可以达到目的。
(1)阻塞式的解决方案:synchronized,Lock
(2)非阻塞式的解决方案:原子变量
(3)synchronized,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
(4)虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:
①互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
②同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

2. synchronized语法及理解

(1)语法

synchronized(对象) // 线程1, 线程2(blocked)
{临界区
}

(2)理解
在这里插入图片描述
①synchronized(对象) 中的对象,可以想象为一个房间(room),有唯一入口(门)房间只能一次进入一人进行计算,线程 t1,t2 想象成两个人
②当线程 t1 执行到 synchronized(room) 时就好比 t1 进入了这个房间,并锁住了门拿走了钥匙,在门内执行count++ 代码
②这时候如果 t2 也运行到了 synchronized(room) 时,它发现门被锁住了,只能在门外等待,自身发生了上下文切换,由运行阶段变为阻塞状态
③这中间即使 t1 的 cpu 时间片不幸用完,被踢出了门外(不要错误理解为锁住了对象就能一直执行下去哦),这时门还是锁住的,t1 仍拿着钥匙,t2 线程还在阻塞状态进不来,只有下次轮到 t1 自己再次获得时间片时才能开门进入
④当 t1 执行完 synchronized{} 块内的代码,这时候才会从 obj 房间出来并解开门上的锁,唤醒 t2 线程把钥匙给他。t2 线程这时才可以进入 obj 房间,锁住了门拿上钥匙,执行它的 count-- 代码

2.1 方法上的 synchronized

class Test{public synchronized void test() {}
}
等价于
class Test{public void test() {synchronized(this) {}}
}
class Test{public synchronized static void test() {}
}
等价于
class Test{public static void test() {synchronized(Test.class) {}}
}

3.变量的线程安全分析

3.1.成员变量和静态变量是否线程安全?

(1)如果它们没有共享,则线程安全
(2)如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
①如果只有读操作,则线程安全
②如果有读写操作,则这段代码是临界区,需要考虑线程安全

3.2.局部变量是否线程安全?

(1)局部变量是线程安全的
(2)但局部变量引用的对象则未必
①如果该对象没有逃离方法的作用访问,它是线程安全的
②如果该对象逃离方法的作用范围,需要考虑线程安全

3.2.1 局部变量线程安全分析

public static void test1() {int i = 10;i++;
}

(1)每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享。如图:
在这里插入图片描述
(2)局部变量引用的对象则稍有不同
①先看一个成员变量的例子

class ThreadUnsafe {ArrayList<String> list = new ArrayList<>();public void method1(int loopNumber) {for (int i = 0; i < loopNumber; i++) {// { 临界区, 会产生竞态条件method2();method3();// } 临界区}}private void method2() {list.add("1");}private void method3() {list.remove(0);}
}

执行
其中一种情况是,如果线程2 还未 add,线程1 remove 就会报错:

Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:657) at java.util.ArrayList.remove(ArrayList.java:496) at cn.itcast.n6.ThreadUnsafe.method3(TestThreadSafe.java:35) at cn.itcast.n6.ThreadUnsafe.method1(TestThreadSafe.java:26) at cn.itcast.n6.TestThreadSafe.lambda$main$0(TestThreadSafe.java:14) at java.lang.Thread.run(Thread.java:748) 

分析:
无论哪个线程中的 method2 引用的都是同一个对象中的 list 成员变量
method3 与 method2 分析相同

②将 list 修改为局部变量那么就不会有上述问题了

class ThreadSafe {public final void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {method2(list);method3(list);}}private void method2(ArrayList<String> list) {list.add("1");}private void method3(ArrayList<String> list) {list.remove(0);}
}

分析:
list 是局部变量,每个线程调用时会创建其不同实例,没有共享
而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
method3 的参数分析与 method2 相同

4.Monitor

4.1 Java 对象头

(1)java的对象头由以下三部分组成:
①Mark Word
Mark Word记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关。
Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit。

在这里插入图片描述

②指向类的指针
该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
Java对象的类数据保存在方法区。
③数组长度(只有数组对象才有)
只有数组对象保存了这部分数据。该数据在32位和64位JVM中长度都是32bit。
(2)普通对象
在这里插入图片描述
(3)数组对象
在这里插入图片描述

4.2 Monitor概念

(1)Monitor被翻译为监视器或管程(由操作系统提供)
(2)每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就会被设置指向Monitor对象的指针
(3)Monitor的结构如下:
在这里插入图片描述
①刚开始Monitor中Owner为null
②当Thread-2执行synchronized(obj)就会将Monitor的所有者Owner置为Thread-2,Monitor中只能有一个Owner
③在Thread-2上锁的过程中,如果Thread-3,Thread-4,Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED
④Thread-2执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争的时候是非公平的
注意:
①synchronized必须是进入同一个对象的monitor才有上述的效果
②不加synchronized的对象不会关联监视器,不遵从以上规则

5.synchronized原理

5.1 轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。轻量级锁对使用者是透明的,即语法仍然是synchronized。例如:
在这里插入图片描述

  • 加锁
    (1)方法被调用时会产生一个栈帧,线程0执行到method1()的synchronized(obj)时会在线程的栈帧中创建锁记录(Lock Record)对象(该对象对我们是不可见的,是JVM层面的),每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word
    在这里插入图片描述

(2)让锁记录中的Object reference指向锁对象,尝试用cas把锁记录中的数据和锁对象中的Mark Word做一个交换,交换是为了表示加锁。
在这里插入图片描述

①如果cas替换成功,对象头中存储了锁记录地址状态00,表示由该线程给对象加锁,这时图示如下
在这里插入图片描述
②如果cas失败,有两种情况:
一种是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程;
另一种是自己执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数
在这里插入图片描述

  • 解锁
    (1)当退出synchronized代码块(解锁时),如果有取值为null的锁记录,表示有重入,这时重置锁记录,重入计数减一
    在这里插入图片描述
    (2)当退出synchronized代码块(解锁时),锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头
    ①成功,则解锁成功
    ②失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

5.2 锁膨胀

如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁
在这里插入图片描述
(1)当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁

在这里插入图片描述

(2)这时Thread-1加轻量级锁失败,进入锁膨胀流程。
①即为Object对象申请Monitor锁,让Object执行指向重量级锁地址。
②然后自己进入Monitor的EntryList BLOCKED
在这里插入图片描述
(3)当Thread-0退出同步代码块解锁时,使用cas将Mark Word的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中BLOCKED线程

5.3 自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功,这时当前线程就可以避免阻塞。(自旋即让这个线程先不进入阻塞,而是进行几次循环,如果在循环的过程持锁线程已经退出了同步块释放了锁,就可以避免阻塞)
(1)自旋重试成功和失败的情况
①自旋重试成功
在这里插入图片描述
②自旋重试失败
在这里插入图片描述
(2)在Java6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,是比较智能的。
(3)自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势。
(4)Java7之后不能控制是否开启自旋功能。

5.4 偏向锁

轻量级锁在没有竞争时(只有自己这个线程),每次重入仍然需要执行CAS操作。Java6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己就表示没有竞争,不用重新CAS,以后只要不发生竞争,这个对象就归该线程所有
例如:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.4.1 偏向状态

在这里插入图片描述
(1)一个对象创建时:
①如果开启了偏向锁(默认开启),那么对象创建后,markword值位0x05即最后3位为101,这时它的thread、epoch、age都为0
②偏向锁是默认延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数
-XX:BiasedLockingStartupDelay=0来禁用延迟
③如果没有开启偏向锁,那么对象创建后,markword值为0x01即最后3位为001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值
(2)禁用偏向锁
添加VM参数 -XX:-UseBiasedLocking禁用偏向锁

5.4.2 撤销偏向锁

5.4.2.1 撤销-调用对象hashCode

调用对象的hashCode()方法,会禁用掉偏向锁。因为如果处于偏向锁的对象头只能存线程ID,存不下哈希码了
在这里插入图片描述

5.4.2.3 撤销-其他线程使用对象

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

5.4.2.4 撤销- 调用wait/notify

只有重量级锁才有wait/notify方法

5.4.3 批量重偏向

(1)如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的Thread ID
(2)当撤销偏向随的阈值超过20次后,jvm会觉得是不是偏向错了,于是会在给这些对象加锁时重新偏向至加锁线程

5.4.4 批量撤销

当撤销偏向锁阈值超过40次后,jvm就会这样觉得,自己是不是偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。

5.4.5 锁消除

锁消除即删除不必要的加锁操作。JVM在运行时,对一些“在代码上要求同步,但是被检测到不可能存在共享数据竞争情况”的锁进行消除。

5.4.6 锁粗化

假设一系列的连续操作都会对同一个对象反复加锁及解锁,甚至加锁操作是出现在循环体中的,即使没有出现线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。如果JVM检测到有一连串零碎的操作都是对同一对象的加锁,将会扩大加锁同步的范围(即锁粗化)到整个操作序列的外部。

6.wait/notify

6.1 wait/notify原理

在这里插入图片描述
(1)线程获取某个对象的Monitor锁,Owner线程发现条件不满足,调用wait方法,即可进入WaiSet变为WAITING状态
(2)BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片
(3)BLOCKED线程会在Owner线程释放锁时唤醒
(4)WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争

6.2 API介绍

  • obj.wait() 让已经进入 object 监视器的线程到 waitSet 等待
  • obj.notify() 让object 上正在 waitSet 等待的线程中挑一个唤醒
  • obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法

final static Object obj = new Object();
public static void main(String[] args) {new Thread(() -> {synchronized (obj) { // 必须获得此对象的锁,才能调用API方法log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}}).start();new Thread(() -> {synchronized (obj) {log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}}).start();// 主线程两秒后执行sleep(2);log.debug("唤醒 obj 上其它线程");synchronized (obj) {obj.notify(); // 唤醒obj上一个线程// obj.notifyAll(); // 唤醒obj上所有等待线程}
}

6.3 wait、notify 的正确使用

(1)sleep(long n) 和 wait(long n) 的区别
①sleep是 Thread 方法,而 wait 是 Object 的方法
②sleep不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
③sleep在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
④ 它们的状态都是TIMED_WAITING

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

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

相关文章

ElasticSearch架构介绍及原理解析

Elasticsearch 是一个高度可扩展的开源全文搜索和分析引擎&#xff0c;用于处理大量的数据。它是由 Elasticsearch BV 公司开发&#xff0c;并且是用 Java 语言编写的。Elasticsearch 基于 Lucene 搜索引擎&#xff0c;提供了 RESTful API&#xff0c;允许你通过 JSON 格式的请…

DFS例题(n皇后问题)C++(Acwing)

代码&#xff1a; #include <iostream>using namespace std;const int N 20;int n; char g[N][N]; bool col[N], dg[N], udg[N];void dfs(int u) {if(u n){for(int i 0; i < n; i) puts(g[i]);puts("");return; }for(int i 0; i < n…

数字化审计智慧

简析内部审计数字化转型的方法和路径 内部审计是一种独立的、客观的确认和咨询活动&#xff0c;包括鉴证、识别和分析问题以及提供管理建议和解决方案。狭义的数字化转型是指将企业经营管理和业务操作的各种行为、状态和结果用数字的形式来记录和存储&#xff0c;据此再对数据进…

【xv6操作系统】Lab systems calls

一、实验前须知 阅读 xv6 文档的第 2 章和第 4 章的 4.3 节和 4.4 节以及相关源文件&#xff1a; 系统调用的用户空间代码在 user/user.h 和 user/usys.pl 中。 内核空间代码在 kernel/syscall.h 和 kernel/syscall.c 中。 与进程相关的代码在 kernel/proc.h 和 kernel/proc.c…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:ImageSpan)

Text组件的子组件&#xff0c;用于显示行内图片。 说明&#xff1a; 该组件从API Version 10开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 ImageSpan(value: ResourceStr | PixelMap) 参数&#xff1a; 参数名参数类…

大模型学习过程记录

一、基础知识 自然语言处理&#xff1a;能够让计算理解人类的语言。 检测计算机是否智能化的方法&#xff1a;图灵测试 自然语言处理相关基础点&#xff1a; 基础点1——词表示问题&#xff1a; 1、词表示&#xff1a;把自然语言中最基本的语言单位——词&#xff0c;将它转…

两天学会微服务网关Gateway-Gateway网关限流

锋哥原创的微服务网关Gateway视频教程&#xff1a; Gateway微服务网关视频教程&#xff08;无废话版&#xff09;_哔哩哔哩_bilibiliGateway微服务网关视频教程&#xff08;无废话版&#xff09;共计17条视频&#xff0c;包括&#xff1a;1_Gateway简介、2_Gateway工作原理、3…

python基础——条件判断和循环【if,while,for,range】

&#x1f4dd;前言&#xff1a; 这篇文章主要讲解一下条件判断语句if和循环语句while&#xff0c;for在python中需要注意的地方。 建议已有一定了解&#xff08;对语句的执行逻辑清楚&#xff09;的读者观看&#xff0c;如果对条件判断和循环的执行逻辑不太清楚&#xff0c;也可…

win11家庭版docker和milvus

docker 1、官网下载docker文件Get Started | Docker&#xff0c;选择download for windows下载。 2、双击打开下载好的文件Docker Desktop Installer.exe&#xff0c;add shortcut to desktop选择√代表同意添加快捷键到桌面&#xff0c;如果不勾选就说明不创建快捷键&#x…

阿里云服务器多少钱1个月?2024年最新报价5元一个月

阿里云服务器一个月多少钱&#xff1f;最便宜5元1个月。阿里云轻量应用服务器2核2G3M配置61元一年&#xff0c;折合5元一个月&#xff0c;2核4G服务器30元3个月&#xff0c;2核2G3M带宽服务器99元12个月&#xff0c;轻量应用服务器2核4G4M带宽165元12个月&#xff0c;4核16G服务…

跨平台大小端判断与主机节序转网络字节序使用

1.macOS : 默认使用小端 ,高位使用高地址,转换为网络字节序成大端 #include <iostream> #include <arpa/inet.h> int main() {//大小端判断union{short s;char c[sizeof(short)];}un;un.s = 0x0102;printf("低地址:%d,高地址:%d\n",un.c[0],un.c[1]);if …

安卓部分手机使用webview加载链接后白屏(Android低版本会出现的问题)

前言 大爷&#xff1a;小伙我这手机怎么打开你们呢这个是白屏什么都不显示。 大娘&#xff1a;小伙我这也是打开你们呢这功能&#xff0c;就是一个白屏什么也没有&#xff0c;你们呢的应用不会有病毒吧。 小伙&#xff1a;我的手机也正常&#xff1b; 同事&#xff1a;我的也正…

【Flink】Flink 的八种分区策略(源码解读)

Flink 的八种分区策略&#xff08;源码解读&#xff09; 1.继承关系图1.1 接口&#xff1a;ChannelSelector1.2 抽象类&#xff1a;StreamPartitioner1.3 继承关系图 2.分区策略2.1 GlobalPartitioner2.2 ShufflePartitioner2.3 BroadcastPartitioner2.4 RebalancePartitioner2…

全栈的自我修养 ———— css中常用的布局方法flex和grid

在项目里面有两种常用的主要布局:flex和grid布局&#xff08;b站布局&#xff09;&#xff0c;今天分享给大家这两种的常用的简单方法&#xff01; 一、flex布局1、原图2、中心对齐3、主轴末尾或者开始对其4、互相间隔 二、grid布局1、基本效果2、加间隔3、放大某一个元素 一、…

Linux第74步_“设备树”下的LED驱动

使用新字符设备驱动的一般模板&#xff0c;以及设备树&#xff0c;驱动LED。 1、添加“stm32mp1_led”节点 打开虚拟机上“VSCode”&#xff0c;点击“文件”&#xff0c;点击“打开文件夹”&#xff0c;点击“zgq”&#xff0c;点击“linux”&#xff0c;点击“atk-mp1”&am…

基于GAN对抗网进行图像修复

一、简介 使用PyTorch实现的生成对抗网络&#xff08;GAN&#xff09;模型&#xff0c;包括编码器&#xff08;Encoder&#xff09;、解码器&#xff08;Decoder&#xff09;、生成器&#xff08;ResnetGenerator&#xff09;和判别器&#xff08;Discriminator&#xff09;。…

LCR 112. 矩阵中的最长递增路径【leetcode】/dfs+记忆化搜索

LCR 112. 矩阵中的最长递增路径 给定一个 m x n 整数矩阵 matrix &#xff0c;找出其中 最长递增路径 的长度。 对于每个单元格&#xff0c;你可以往上&#xff0c;下&#xff0c;左&#xff0c;右四个方向移动。 不能 在 对角线 方向上移动或移动到 边界外&#xff08;即不允…

前端覆盖率报告生成

前端精准测试是精准测试体系的一部分&#xff0c;但是由于前端项目比较灵活&#xff0c;各种框架&#xff0c;脚手架再加上开发同学写的不够规范&#xff0c;所以投入产出比较低&#xff0c;这部分内容在网上的资料也比较少。为了完善我们的精准测试体系&#xff0c;今年做了前…

21、状态模式(行为性模式)

版本一、get状态指针 #include <iostream> using namespace std;//前置声明 class Context;//状态 class State{ public://4个状态virtual void toUp (Context& context){ }virtual void toDown (Context& context){ }virtual void toLeft (Context& cont…

学习和认知的四个阶段,以及学习方法分享

本文分享学习的四个不同的阶段&#xff0c;以及分享个人的一些学习方法。 一、学习认知的四个阶段 我们在学习的过程中&#xff0c;总会经历这几个阶段&#xff1a; 第一阶段&#xff1a;不知道自己不知道&#xff1b; 第二阶段&#xff1a;知道自己不知道&#xff1b; 第三…