【JAVA基础】多线程与线程池

多线程与线程池

文章目录

  • 多线程与线程池
    • 1. 相关概念
      • 1.1 线程调度
      • 1.2 守护线程
    • 2. 生命周期
    • 3. 同步机制/同步锁
      • 3.1 synchronized
      • 3.2 lock
      • 3.3 synchronized 与 Lock 的对比
    • 4. 死锁
    • 5. 线程通信
      • 5.1 线程间的通信
      • 5.2 等待唤醒机制
      • 5.3 举例
      • 5.4 调用 wait 和 notify 需注意的细节
      • 5.5 生产者消费者问题
    • 6. 线程池
      • 6.1七大核心属性
      • 6.2线程池处理流程图
        • 6.2.1 线程池处理
        • 6.2.2 addWorker方法

1. 相关概念

1.1 线程调度

  • 分时调度:所有线程轮流使用 CPU 的使用权,并且平均分配每个线程占用 CPU 的时间。
  • 抢占式调度:让优先级高的线程以较大的概率优先使用 CPU。如果线程的优先级相同,那么会随机选择一个(线程随机性),Java 使用的为抢占式调度。

每个线程都有一定的优先级,同优先级线程组成先进先出队列(先到先服务),使用分时调度策略。优先级高的线程采用抢占式策略,获得较多的执行
机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。:

  • Thread 类的三个优先级常量:

    • MAX_PRIORITY(10):最高优先级
    • MIN _PRIORITY (1):最低优先级
    • NORM_PRIORITY (5):普通优先级,默认情况下 main 线程具有普通优先级

1.2 守护线程

有一种线程,它是在后台运行的,它的任务是为其他线程提供服务的,这种线程被称为“守护线程”。JVM 的垃圾回收线程就是典型的守护线程。守护线程有个特点,就是如果所有非守护线程都死亡,那么守护线程自动死亡。形象理解:兔死狗烹,鸟尽弓藏

2. 生命周期

在 java.lang.Thread.State 的枚举类中这样定义:

public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}
  • NEW(新建):线程刚被创建,但是并未启动。还没调用 start 方法。

  • RUNNABLE(可运行):这里没有区分就绪和运行状态。因为对于 Java 对象来说,只能标记为可运行,至于什么时候运行,不是 JVM 来控制的了,是 OS 来进行调度的,而且时间非常短暂,因此对于 Java 对象的状态来说,无法区分。

  • Teminated(被终止):表明此线程已经结束生命周期,终止运行。

  • BLOCKED(锁阻塞):在 API 中的介绍为:一个正在阻塞、等待一个监视器锁(锁对象)的线程处于这一状态。只有获得锁对象的线程才能有执行机会。

    比如,线程 A 与线程 B 代码中使用同一锁,如果线程 A 获取到锁,线程 A 进入到 Runnable 状态,那么线程 B 就进入到 Blocked锁阻塞状态。

  • TIMED_WAITING(计时等待):在 API 中的介绍为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。

    当前线程执行过程中遇到 Thread 类的 sleep 或 join,Object 类的 wait,LockSupport 类的 park 方法,并且在调用这些方法时,设置了时间,那么当前线程会进入 TIMED_WAITING,直到时间到,或被中断。

  • WAITING(无限等待):在 API 中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。

    当前线程执行过程中遇到遇到 Object 类的 wait,Thread 类的join,LockSupport 类的 park 方法,并且在调用这些方法时,没有指定时间,那么当前线程会进入 WAITING 状态,直到被唤醒。

说明:当从 WAITING 或 TIMED_WAITING 恢复到 Runnable 状态时,如果发现当前线程没有得到监视器锁,那么会立刻转入 BLOCKED 状态。

在这里插入图片描述

3. 同步机制/同步锁

3.1 synchronized

同步锁对象可以是任意类型,但是必须保证竞争“同一个共享资源”的多个线程必须使用同一个“同步锁对象”。

3.2 lock

  • 保证线程的安全。与采用 synchronized 相比,Lock 可提供多种锁方案,更灵活、更强大。Lock 通过显式定义同步锁对象来实现同步。同步锁使用Lock 对象充当。

  • java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。

  • 在实现线程安全的控制中,比较常用的是 ReentrantLock,可以显式加锁、释放锁。

    ReentrantLock 类实现了 Lock 接口,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。

3.3 synchronized 与 Lock 的对比

  1. Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized 是隐式锁,出了作用域、遇到异常等自动解锁

  2. Lock 只有代码块锁,synchronized 有代码块锁和方法锁

  3. 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类,读写锁等),更体现面向对象。

说明:开发建议中处理线程安全问题优先使用顺序为:Lock ----> 同步代码块 ----> 同步方法

4. 死锁

同步机制带来的死锁问题:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

  • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 循环等待:存在一个进程或线程的资源申请序列,使得每个进程或线程都在等待下一个进程或线程所持有的资源。

解决死锁:死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。

  • 针对互斥条件:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
  • 针对请求和保持条件:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
  • 针对不剥夺条件:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
  • 针对循环等待:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

5. 线程通信

5.1 线程间的通信

当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。

比如:线程 A 用来生产包子的,线程 B 用来吃包子的,包子可以理解为同一资源,线程 A 与线程 B 处理的动作,一个是生产,一个是消费,此时 B 线程必须等到 A 线程完成后才能执行,那么线程 A 与线程 B 之间就需要线程通信,即—— 等待唤醒机制。

5.2 等待唤醒机制

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。在一个线程满足某个条件时,就进入等待状态(wait() / wait(time)), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());或可以指定wait 的时间,等时间到了自动唤醒;在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。

  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态是 WAITING 或 TIMED_WAITING。它
    还要等着别的线程执行一个特别的动作,也即“通知(notify)”或者等待时间到,在这个对象上等待的线程从 wait set 中释放出来,重新进入到调度队(ready queue)中
  2. notify:则选取所通知对象的 wait set 中的一个线程释放;
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

注意:被通知的线程被唤醒后也不一定能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以它需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

  • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE(可运行)状态;
  • 否则,线程就从 WAITING 状态又变成 BLOCKED(等待锁) 状态

注意:在JUC中将会学到更多有关等待唤醒机制的类与方法,建议使用JUC中学习到的

5.3 举例

例题:使用两个线程打印 1-100。线程 1, 线程 2 交替打印

class Communication implements Runnable {int i = 1;public void run() {while (true) {synchronized (this) {notify();if (i <= 100) {System.out.println(Thread.currentThread().getName() + ":" + i++);} else break;try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}}
}

5.4 调用 wait 和 notify 需注意的细节

  1. wait 方法与 notify 方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过 notify 唤醒使用同一个锁对象调用的 wait 方法后的线程。
  2. wait 方法与 notify 方法是属于 Object 类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了 Object 类的。
  3. wait 方法与 notify 方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这 2 个方法。否则会报 java.lang.IllegalMonitorStateException 异常。

5.5 生产者消费者问题

等待唤醒机制可以解决经典的“生产者与消费者”的问题。生产者与消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个(多个)共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。

生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

举例:

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

生产者与消费者问题中其实隐含了两个问题:

  • 线程安全问题:因为生产者与消费者共享数据缓冲区,产生安全问题。不过这个问题可以使用同步解决。
  • 线程的协调工作问题:要解决该问题,就必须让生产者线程在缓冲区满时等待(wait),暂停进入阻塞状态,等到下次消费者消耗了缓冲区中的数据的时候,通知(notify)正在等待的线程恢复到就绪状态,重新开始往缓冲区添加数据。同样,也可以让消费者线程在缓冲区空时进入等待(wait),暂停进入阻塞状态,等到生产者往缓冲区添加数据之后,再通知(notify)正在等待的线程恢复到就绪状态。通过这样的通信机制来解决此类问题。

代码实现:

public class ConsumerProducerTest {public static void main(String[] args) {Clerk clerk = new Clerk();Producer p1 = new Producer(clerk);Consumer c1 = new Consumer(clerk);Consumer c2 = new Consumer(clerk);p1.setName("生产者 1");c1.setName("消费者 1");c2.setName("消费者 2");p1.start();c1.start();c2.start();}
}//生产者
class Producer extends Thread{private Clerk clerk;public Producer(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {System.out.println("=========生产者开始生产产品========");while(true){try {Thread.sleep(40);} catch (InterruptedException e) {e.printStackTrace();}//要求 clerk 去增加产品clerk.addProduct();}}
}//消费者
class Consumer extends Thread{private Clerk clerk;public Consumer(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {System.out.println("=========消费者开始消费产品========");while(true){try {Thread.sleep(90);} catch (InterruptedException e) {e.printStackTrace();}//要求 clerk 去减少产品clerk.minusProduct();}}
}//资源类,缓冲区
class Clerk {private int productNum = 0;//产品数量private static final int MAX_PRODUCT = 20;private static final int MIN_PRODUCT = 1;//增加产品public synchronized void addProduct() {if(productNum < MAX_PRODUCT){productNum++;System.out.println(Thread.currentThread().getName() +"生产了第" + productNum + "个产品");//唤醒消费者this.notifyAll();}else{try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}//减少产品public synchronized void minusProduct() {if(productNum >= MIN_PRODUCT){System.out.println(Thread.currentThread().getName() +"消费了第" + productNum + "个产品");productNum--;//唤醒生产者this.notifyAll();}else{try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}
}

6. 线程池

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要的代价较高。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

注意:线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。

引用阿里《Java开发手册》中的一段描述:

【强制】线程池不允许使用Executors创建,建议通过ThreadPoolExecutor的方式创建,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:

1.FixedThreadPool和SingleThreadPool:

允许的请求队列长度为Integet.MAX_VALUE,可能会堆积大量的请求从而导致OOM;

2.CachedThreadPool:

允许创建线程数量为Integet.MAX_VALUE,可能会创建大量的线程,从而导致OOM.

6.1七大核心属性

  1. corePoolSize(int):核心线程数量。默认情况下,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到任务队列当中。线程池将长期保证这些线程处于存活状态,即使线程已经处于闲置状态。除非配置了allowCoreThreadTimeOut=true,核心线程数的线程也将不再保证长期存活于线程池内,在空闲时间超过keepAliveTime后被销毁。
  2. workQueue:阻塞队列,存放等待执行的任务,线程从workQueue中取任务,若无任务将阻塞等待。当线程池中线程数量达到corePoolSize后,就会把新任务放到该队列当中。JDK提供了四个可直接使用的队列实现,分别是:基于数组的有界队列ArrayBlockingQueue、基于链表的无界队列LinkedBlockingQueue、只有一个元素的同步队列SynchronousQueue、优先级队列PriorityBlockingQueue。在实际使用时一定要设置队列长度
  3. maximumPoolSize(int):线程池内的最大线程数量,线程池内维护的线程不得超过该数量,大于核心线程数量小于最大线程数量的线程将在空闲时间超过keepAliveTime后被销毁。当阻塞队列存满后,将会创建新线程执行任务,线程的数量不会大于maximumPoolSize。
  4. keepAliveTime(long):线程存活时间,若线程数超过了corePoolSize,线程闲置时间超过了存活时间,该线程将被销毁。除非配置了allowCoreThreadTimeOut=true,核心线程数的线程也将不再保证长期存活于线程池内,在空闲时间超过keepAliveTime后被销毁。
  5. TimeUnit unit:线程存活时间的单位,例如TimeUnit.SECONDS表示秒。
  6. RejectedExecutionHandler:拒绝策略,当任务队列存满并且线程池个数达到maximunPoolSize后采取的策略。ThreadPoolExecutor中提供了四种拒绝策略,分别是:抛RejectedExecutionException异常的AbortPolicy(默认策略)、使用调用者所在线程来运行任务CallerRunsPolicy、丢弃一个等待执行的任务,然后尝试执行当前任务DiscardOldestPolicy、不动声色的丢弃并且不抛异常DiscardPolicy。项目中如果为了更多的用户体验,可以自定义拒绝策略。
  7. threadFactory:创建线程的工厂,虽说JDK提供了线程工厂的默认实现DefaultThreadFactory,但还是建议自定义实现最好,这样可以自定义线程创建的过程,例如线程分组、自定义线程名称等以进行线程监控

6.2线程池处理流程图

6.2.1 线程池处理

在这里插入图片描述

拒绝策略:RejectedExecutionHandler

当任务队列和线程池都满了时所采取的应对策略,默认是AbordPolicy,表示无法处理新任务,并抛出RejectedExecutionException异常。此外还有3种策略:

  • CallerRunsPolicy:用调用者所在的线程处理任务。此策略提供简单的反馈机制,能够减缓新任务的提交速度。
  • DiscardPolicy:不能执行任务,并将任务删除。
  • DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。
6.2.2 addWorker方法

该方法返回false则会执行拒绝策略方法

主流程图:

在这里插入图片描述

流程中去除一些异常情况,只留了主要流程,流程中有一步验证线程数大于核心线程或者最大线程数,如果传递的参数core等于true那么运行线程数量不能大于核心线程数量,如果为false则当前线程数量不能大于最大。

addWorker只有两个作用:增加工作线程数量、创建一个Worker并加到工作线程集合中。

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

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

相关文章

进阶课4——随机森林

1.定义 随机森林是一种集成学习方法&#xff0c;它利用多棵树对样本进行训练并预测。 随机森林指的是利用多棵树对样本进行训练并预测的一种分类器&#xff0c;每棵树都由随机选择的一部分特征进行训练和构建。通过多棵树的集成&#xff0c;可以增加模型的多样性和泛化能力。…

c++视觉检测------Shi-Tomasi 角点检测

Shi-Tomasi 角点检测 &#xff1a;goodFeaturesToTrack() goodFeaturesToTrack() 函数是 OpenCV 中用于角点检测的功能函数。它的主要作用是检测图像中的良好特征点&#xff0c;通常用于计算机视觉任务中的光流估算、目标跟踪等。 函数签名&#xff1a; void goodFeaturesTo…

pytorch实战---IMDB情感分析

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

Pytorch公共数据集、tensorboard、DataLoader使用

本文将主要介绍torchvision.datasets的使用&#xff0c;并以CIFAR-10为例进行介绍&#xff0c;对可视化工具tensorboard进行介绍&#xff0c;包括安装&#xff0c;使用&#xff0c;可视化过程等&#xff0c;最后介绍DataLoader的使用。希望对你有帮助 Pytorch公共数据集 torc…

vscode安装可以打开docx文件的插件

文章目录 vscode安装可以打开docx文件的插件去插件商城搜索并安装安装后打开一个word vscode安装可以打开docx文件的插件 去插件商城搜索并安装 安装后 打开一个word

脏牛提权 liunx

使用方法 Liunx 普通用户 内核版本 在版本里 我直接脏牛提权 有脚本查看内核版本 上传c脚本 编译 直接执行 获取高权限 提权 Liunx https://github.com/InteliSecureLabs/Linux Exploit Suggester 运行这个脚本 上传到客户端 https://github…

Unity Hub报错:No valid Unity Editor license found. Please activate your license.

最近 遇到一个问题&#xff0c;打开高版本时Hub抛出异常&#xff1a;No valid Unity Editor license found. Please activate your license. 首先你必须排除是否登录Unity Hub&#xff0c;并且激活许可证。 方法一&#xff1a;禁用网络&#xff08;这个可能无效&#xff09; …

如何通过卖虚拟资料月入10万?看这几个卖资料案例

我微信好友里&#xff0c;有近4000个是做创业博主的同行。 你可能会好奇&#xff0c;其中60%的人都通过卖虚拟资料起家&#xff0c;这到底说明了什么呢&#xff1f; 嗯&#xff0c;事实上&#xff0c;这就意味着这些人选择了网络赚钱的首选项目&#xff0c;那就是销售各种资料…

python selenium如何带cookie访问网站

python selenium如何带cookie访问网站 要使用Python的Selenium库带有cookie访问网站&#xff0c;你可以按照以下步骤进行操作&#xff1a; 一、流程介绍 安装Selenium库&#xff08;如果尚未安装&#xff09;&#xff1a; pip install selenium导入Selenium库并启动一个浏览…

导致爬虫无法使用的原因有哪些?

随着互联网的普及和发展&#xff0c;爬虫技术也越来越多地被应用到各个领域。然而&#xff0c;在实际使用中&#xff0c;爬虫可能会遇到各种问题导致无法正常工作。本文将探讨导致爬虫无法使用的原因&#xff0c;并给出相应的解决方法。 一、目标网站反爬虫机制 许多网站为了…

11 个最值得推荐的 Windows 数据恢复软件

您可能已经尝试过许多免费的恢复程序&#xff0c;但它们都不起作用&#xff0c;对吧&#xff1f;这就是您正在寻找最好的数据恢复软件的原因。 个人去过那里。根据个人的经验&#xff0c;大多数免费软件并不能解决这个问题。有时&#xff0c;当个人在 PC 上运行恢复程序时&…

【API篇】七、Flink窗口

文章目录 1、窗口2、分类3、窗口API概览4、窗口分配器 在批处理统计中&#xff0c;可以等待一批数据都到齐后&#xff0c;统一处理。但是在无界流的实时处理统计中&#xff0c;是来一条就得处理一条&#xff0c;那么如何统计最近一段时间内的数据呢&#xff1f; ⇒ 窗口的概念&…

选择合适的项目管理系统来支持专业产品研发团队

专业产品研发团队的公司离不开其严谨的管理和高效的研发流程&#xff0c;为了进一步提升研发效率和管理水平&#xff0c;产研团队需要一个全流程的项目管理系统来支持其研发团队的协同合作。 一、系统需求 IT行业的研发工作涵盖了从立项、项目变更到项目的进程计划等多个环节。…

B-tree(PostgreSQL 14 Internals翻译版)

概览 B树(作为B树访问方法实现)是一种数据结构&#xff0c;它使您能够通过从树的根向下查找树的叶节点中所需的元素。为了明确地标识搜索路径&#xff0c;必须对所有树元素进行排序。B树是为有序数据类型设计的&#xff0c;这些数据类型的值可以进行比较和排序。 下面的机场代…

OpenCV视频车流量识别详解与实践

视频车流量识别基本思想是使用背景消去算法将运动物体从图片中提取出来&#xff0c;消除噪声识别运动物体轮廓&#xff0c;最后&#xff0c;在固定区域统计筛选出来符合条件的轮廓。 基于统计背景模型的视频运动目标检测技术&#xff1a; 背景获取&#xff1a;需要在场景存在…

基于PHP的线上购物商城,MySQL数据库,PHPstudy,原生PHP,前台用户+后台管理,完美运行,有一万五千字论文。

目录 演示视频 基本介绍 论文截图 功能结构 系统截图 演示视频 基本介绍 基于PHP的线上购物商城&#xff0c;MySQL数据库&#xff0c;PHPstudy&#xff0c;原生PHP&#xff0c;前台用户后台管理&#xff0c;完美运行&#xff0c;有一万五千字论文。 现如今,购物网站是商业…

【七】SpringBoot为什么可以打成 jar包启动

SpringBoot为什么可以打成 jar包启动 简介&#xff1a;庆幸的是夜跑的习惯一直都在坚持&#xff0c;正如现在坚持写博客一样。最开始刚接触springboot的时候就觉得很神奇&#xff0c;当时也去研究了一番&#xff0c;今晚夜跑又想起来了这茬事&#xff0c;于是想着应该可以记录一…

Python单元测试

import unittest #必须要导入单元测试的包class Student(object):def __init__(self, name, score):self.name nameself.score scoredef get_grade(self):if self.score > 100:#返回错误不能用return&#xff0c;应该用raise raise ValueError("成绩不能大于100"…

MySQL进阶(日志)——MySQL的日志 bin log (归档日志) 事务日志redo log(重做日志) undo log(回滚日志)

前言 MySQL最为最流行的开源数据库&#xff0c;其重要性不言而喻&#xff0c;也是大多数程序员接触的第一款数据库&#xff0c;深入认识和理解MySQL也比较重要。 本篇博客阐述MySQL的日志&#xff0c;介绍重要的bin log (归档日志) 、 事务日志redo log(重做日志) 、 undo lo…

【iOS逆向与安全】某音App直播间自动发666 和 懒人自动看视频

1.目标 由于看直播的时候主播叫我发 666&#xff0c;支持他&#xff0c;我肯定支持他呀&#xff0c;就一直发&#xff0c;可是后来发现太浪费时间了&#xff0c;能不能做一个直播间自动发 666 呢&#xff1f;于是就花了几分钟做了一个。 2.操作环境 越狱iPhone一台 frida m…