Java多线程<二>多线程经典场景

leetcode 多线程刷题

  1. 上锁上一次,还是上多次?

  2. 同步的顺序。

1. 交替打印字符

  • 使用sychronize同步锁
  • 使用lock锁
  • 使用concurrent的默认机制
  • 使用volitale关键字 + Thread.sleep() / Thread.yield机制
  • 使用automic原子类

方式1 :使用互斥访问state + Number中控制当前state进行

  • 实现1:使用synchornized上锁,wait让出cpu
  • 实现2:使用semophore上锁, sleep或者yield让出cpu
  • 实现3:使用原子Integer进行访问 + yield或者sleep让出cpu
  • 实现4:使用Lock进行访问 + condition让出cpu
  • 实现5: 使用blockingQueue放入state,如果不是自己的state,在放进去,然后让出cpu。

方式2:使用互斥访问全局cur进行,cur代表当前数字是多少,如果cur >= n,就直接return让线程终止。

  • 其中cur代表的是当前的数字是多少。
  • 互斥的访问方式仍然是上面的那些种。

方式3:使用同步的通知模式

上面的方式,四个线程都是一直处于活跃状态,也就是Runnable的状态。(使用wait的除外)。另外判断是否可以运行都需要while进行判断。

但实际上,四个线程在同一时间,只需要一个线程可以运行。其他线程都必须进行阻塞。所以可以使用同步通知的方式进行,在其他线程运行的时候,阻塞另外的三个线程,并且运行完成一个线程后,可以实现精准通知另一个线程启动

2. 打印0和奇偶数字

  1. 使用锁 sychornized和Lock

  2. 使用并发工具

    • barrier

    • semopher

  3. 使用cas + Thread.sleep/volatile + ThreadSleep

  4. 使用blocking que进行实现

经典模型

1. 生产者消费者的几种实现方式

  1. 操作系统课本上的经典信号量机制。
    • 锁使用synchornized关键字
    • 加上while(true)死循环
package cn.itedus.lottery.test;import lombok.SneakyThrows;import java.util.Stack;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantLock;/*** @author: Zekun Fu* @date: 2023/11/13 11:28* @Description:*/public class test434 {static Stack<String> que = new Stack<>();static Object full = new ReentrantLock();static Object empty = new ReentrantLock();static ReentrantLock lock = new ReentrantLock();static int n = 0;static final  int st = 10;static class Consumer {void consume() {while (true) {lock.lock();if (n > 0) {lock.unlock();System.out.println("消费者消费..." + que.pop());n--;synchronized (full) {full.notifyAll();}} else {lock.unlock();synchronized (empty) {try {empty.wait();} catch (InterruptedException e) {e.printStackTrace();}}}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}static class Producer {void produce() {while (true) {lock.lock();if (n < st) {lock.unlock();String id = "" + (int)(Math.random() * 100);System.out.println("生产者生产..." + id);que.add(id);n++;synchronized (empty) {empty.notifyAll();}} else {lock.unlock();synchronized (full) {try {full.wait();} catch (InterruptedException e) {e.printStackTrace();}}}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {Producer p = new Producer();p.produce();}}).start();new Thread(new Runnable() {@Overridepublic void run() {new Consumer().consume();}}).start();}
}
  1. 使用阻塞队列进行实现。 --> Java实现好的生产者消费者
  2. 手动加锁进行实现。 -->

2. 哲学家进餐

3. 读者写者

4. 并行的统计

  • 第一个例子是课本上的匹配问题。
  • 对于每一个文件夹可以使用线程池进行一个新的线程创建
  • 最后对Future进行统计
package threadBase.threadPool;/*
*
*
*   java核心技术卷上面的线程池
* 使用线程池统计文件中含有关键字的文件个数
*  默认一个文件夹开启一个线程进行处理*/import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.*;public class Test1 {public static void main(String[] args) {String dir = "D:\\projects\\java\\javaBase\\threads\\data";System.out.println("文件夹的绝对路径是: " + dir);ExecutorService pool = Executors.newCachedThreadPool();String keyWord = "this";System.out.println("关键词是: " + keyWord);MatchCounter counter = new MatchCounter(pool, keyWord, new File(dir));Future<Integer> result = pool.submit(counter);try {System.out.println("含有关键词的文件个数为:" + result.get());} catch (Exception e) {e.printStackTrace();}int largestPoolSize = ((ThreadPoolExecutor)pool).getLargestPoolSize();System.out.println("线程池的最大数量是:" + largestPoolSize);pool.shutdown();                // 别忘了关闭线程池}
}class MatchCounter implements Callable<Integer> {private ExecutorService pool;private String keyWord;private File dir;public MatchCounter(ExecutorService pool, String keyWord, File dir) {this.pool = pool;this.keyWord = keyWord;this.dir = dir;}@Overridepublic Integer call() throws Exception {int cnt = 0;try {File[] files = dir.listFiles();List<Future<Integer>> ress = new ArrayList<>();for (File f: files) {           // 分治if (f.isDirectory()) {      // 开启新线程,从线程池中MatchCounter mc = new MatchCounter(pool, keyWord, f);Future<Integer>res = pool.submit(mc);ress.add(res);}else {                      // 如果是文件直接计算if (search(f)) cnt++;}}for (Future<Integer>res : ress) {cnt += res.get();}}catch (Exception e) {e.printStackTrace();}return cnt;}public boolean search(File file) {try {try (Scanner sc = new Scanner(file, "UTF-8")){boolean flag = false;while(!flag && sc.hasNextLine()) {String line = sc.nextLine();if (line.contains(keyWord)) flag = true;}return flag;}} catch (Exception e) {e.printStackTrace();}return false;}
}

5.并行的搜索

  • bfs的每一个结点开启一个新的线程进行搜索,使用并发的Map作为vis数组,使用并发的queue存入结点,同时使用并发的List放入结点。
  • 适用于请求子节点会需要大量的时间的情况,这种适合一个异步的操作。在请求的时候,对以前请求到的结点进行一个过滤和统计。
package leetcode;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;/*
*
*   使用线程池 + future进行爬取
*
*
* */
public class Crawl4 {HashMap<String, List<String>> G = new HashMap<>();private class HtmlParser {List<String>getUrls(String start) {if (G.containsKey(start)) {List<String>ans = G.get(start);System.out.println("start = " + start + ", sz = "+ ans.size());return ans;}return new ArrayList<>();}}String hostName;private ConcurrentHashMap<String, Boolean> totalUrls = new ConcurrentHashMap<>();public List<String> crawl(String startUrl, HtmlParser htmlParser) {// bfs开始hostName = extractHostName(startUrl);ExecutorService pool = Executors.newCachedThreadPool();Future<List<String>>taskRes = pool.submit(new Chore(this, htmlParser, startUrl, pool));List<String>ans = new ArrayList<>();try {ans = taskRes.get();}catch (Exception e) {e.printStackTrace();}pool.shutdown();// System.out.println("最大的线程数量:" + ((ThreadPoolExecutor)pool).getLargestPoolSize());return ans;}private class Chore implements Callable<List<String>> {private Crawl4 solution;private HtmlParser htmlParser;private String urlToCrawl;private ExecutorService pool;public Chore(Crawl4 solution, HtmlParser htmlParser, String urlToCrawl, ExecutorService pool) {this.solution = solution;this.htmlParser = htmlParser;this.pool = pool;this.urlToCrawl = urlToCrawl;}@Overridepublic List<String> call() throws Exception {//            System.out.println("url = " + urlToCrawl);// 此处不需要使用并发的,因为统计只有主线程进行List<String>ans = new ArrayList<>();ans.add(urlToCrawl);this.solution.totalUrls.put(urlToCrawl, true);List<String> urls = htmlParser.getUrls(urlToCrawl);List<Future<List<String>>> ress = new ArrayList<>();for (String url : urls) {       // 每一个结点开启一个新的线程进行计算if (this.solution.totalUrls.containsKey(url)) continue;this.solution.totalUrls.put(url, true);String hostName = this.solution.extractHostName(url);if (!hostName.equals(this.solution.hostName)) continue;Chore c = new Chore(solution, htmlParser, url, pool);Future<List<String>> res = pool.submit(c);ress.add(res);}// 计算完成所有的任务,直接进行返回就行了for (Future<List<String>>f:ress) {ans.addAll(f.get());}return ans;}}private String extractHostName(String url) {String processedUrl = url.substring(7);int index = processedUrl.indexOf("/");if (index == -1) {return processedUrl;} else {return processedUrl.substring(0, index);}}public void build(int[][] edges) {String[] s = {"http://news.yahoo.com","http://news.yahoo.com/news","http://news.yahoo.com/news/topics/","http://news.google.com"};for (int[] e : edges) {String u = s[e[0]];String v = s[e[1]];if (G.containsKey(u)) {G.get(u).add(v);} else {List<String> l = new ArrayList<>();l.add(v);G.put(u, l);}}
//        for (String t : G.get("http://news.yahoo.com/news/topics/")) {
//            System.out.println(t);
//        }}public static void main(String[] args) {Crawl4 c = new Crawl4();String input = " [[0,2],[2,1],[3,2],[3,1],[3,0],[2,0]]";input = input.replace("[", "{");input = input.replace("]", "}");System.out.println(input);int[][] edges =   {{0,2},{2,1},{3,2},{3,1},{3,0},{2,0}};c.build(edges);List<String> ans = c.crawl("http://news.yahoo.com/news/topics/", c.new HtmlParser());for (String s: ans) {System.out.println(s);}}}

线程对效率的影响

1. 实现分治计算数组和

task放在循环外面

一个小实验,用来测试线程对性能的提升

  1. 计算数组中每一个数字乘以100的和。
  2. 使用双重循环计算,不用数学公式,这样计算时间长一点,容易做对比。
  3. 总共实现了四种不同的方式
    • 使用单线程
    • 使用4个手动写的线程
    • 使用分治,先拷贝数组分成t份,之后进行合并
    • 使用for循环生成4个手写的线程。

最后可以看到手动实现4个线程进行分治可以把效率提升到4倍左右

详细代码请看下面代码1。
在这里插入图片描述

分析1:

  • 由于我的计算机是8核16线程的,所以最多可以实现16个线程的并行计算。所以4个线程最多可以把效率提升到4倍。但是最多的效率提升就到16倍了。
  • 使用分治,由于由大量的数组拷贝,所以计算的效率会低很多。
  • 使用for循环创建线程,由于task的get()是阻塞的,会导致for循环没法执行,从而使得下面的线程没法执行。解决办法是
    • 保存生成的task, 在for循环外面调用get方法。
    • 之后的效率如下。

详细代码请看下面代码2。

在这里插入图片描述

分析2:提升线程个数带来的影响

  • 可以看到最终的效率提升了15倍左右。
  • 这是由于16个逻辑处理器并行工作的原因。
  • 这么一看电脑设计的还不错。16个逻辑处理器能并行提升15倍的性能。这还是在上下文切换的情况下。在我代码垃圾的情况下。hhhh

详细代码请看下面代码3。

在这里插入图片描述
在这里插入图片描述

代码1

package threadBase.baseKey;import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Random;
import java.util.Stack;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;/*
*
*
*   线程启动的三种方式
* 1. implement
* 2. extends
* 3. FutureT
*
* 4.
* */
public class StartThread {private static final int MAXN = 100000000;private static int[] nums = new int[MAXN];     // 计算数组中100个数字的和private AtomicInteger cur = new AtomicInteger();static {Arrays.fill(nums, 1);}private static long testSingle() throws Exception {long startTime = System.currentTimeMillis();long sum = 0;for (int i = 0; i < MAXN; i++) {for (int j = 0; j < 100; j++) {sum += nums[i];}}long endTime = System.currentTimeMillis();System.out.println("单线程: ");System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");return endTime - startTime;}private static long test1() throws Exception{long startTime = System.currentTimeMillis();FutureTask<Long> task1 = new FutureTask<Long>(() -> {long tsum = 0;for (int i = 0; i < 25000000; i++) {for (int j = 0; j < 100; j++) {tsum += nums[i];}}return tsum;});FutureTask<Long> task2 = new FutureTask<Long>(() -> {long tsum = 0;for (int i = 25000000; i < 50000000; i++) {for (int j = 0; j < 100; j++) {tsum += nums[i];}}return tsum;});FutureTask<Long> task3 = new FutureTask<Long>(() -> {long tsum = 0;for (int i = 50000000; i < 75000000; i++) {for (int j = 0; j < 100; j++) {tsum += nums[i];}}return tsum;});FutureTask<Long> task4 = new FutureTask<Long>(() -> {long tsum = 0;for (int i = 75000000; i < 100000000; i++) {for (int j = 0; j < 100; j++) {tsum += nums[i];}}return tsum;});new Thread(task1).start();new Thread(task2).start();new Thread(task3).start();new Thread(task4).start();long sum = task1.get() + task2.get() + task3.get() + task4.get();long endTime = System.currentTimeMillis();System.out.println("4线程:");System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");return endTime - startTime;}private static long test2() throws Exception{/***   首先需要一个线程进行任务的划分。*  然后由这个划分线程生成划分任务的线程数目。* 在有这个线程进程组装。* 在这使用主线程进行划分。* */int t = 5;                  // 划分线程的数量int len = MAXN / t;long sum = 0;long startTime = System.currentTimeMillis();for (int i = 0; i < t; i++) {           // 进行任务划分int[] numt = new int[len];for (int j = i * len; j < (i + 1) * len; j++) {numt[j - (i * len)] = nums[j];}// 线程执行FutureTask<Long>task = new FutureTask<Long>(()->{long ans = 0;for (int x: numt) {for (int j = 0; j < 100; j++) {ans += x;}}return ans;});new Thread(task).start();sum += task.get();}long endTime = System.currentTimeMillis();System.out.println("使用分治进行" + t + "线程划分执行:");System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");return endTime - startTime;}private static long test3() throws Exception {StartThread startThread = new StartThread();int cnt = 4;                    // 控制线程的个数int sz = MAXN / cnt;            // 每一个线程计算的数量是多少long sum = 0;                        // 计算和是多少long startTime = System.currentTimeMillis();for (int i = 0; i < cnt; i++) {FutureTask<Long> task = new FutureTask<Long>(()->{long ans = 0L;int bg = startThread.cur.getAndIncrement();for (int j = bg * sz; j < (bg + 1) * sz; j++) {for (int k = 0; k <100; k++) {ans += nums[j];}}return ans;});new Thread(task).start();sum += task.get();}long endTime = System.currentTimeMillis();System.out.println(cnt + "个线程:");System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");return endTime - startTime;}// 可以从第三遍开始,统计一个平均的时间public static void main(String[] args)throws Exception {long t1 = 0, t2 = 0, t3 = 0, t4 = 0;for (int i = 0; i < 11; i++) {      // 后面的8轮次进行统计System.out.println("-----------第" + i + "轮----------");if (i >= 3) {t1 += testSingle();t2 += test1();t3 += test2();t4 += test3();} else {testSingle();test1();test2();test3();}}System.out.println("平均时间:");System.out.println("单线程:" + t1 / 8 + "ms");System.out.println("4个手动多线程:" + t2 / 8 + "ms");System.out.println("4个分治多线程:" + t3 / 8 + "ms");System.out.println("for循环多线程:" + t4 / 8 + "ms");}
}

代码2

主要就是修改了test2和test3的方法。

  • 把task.get()放在循环外面计算。
  • 使用数组保存生成的task。
    private static long test2() throws Exception{/***   首先需要一个线程进行任务的划分。*  然后由这个划分线程生成划分任务的线程数目。* 在有这个线程进程组装。* 在这使用主线程进行划分。* */int t = 5;                  // 划分线程的数量int len = MAXN / t;long sum = 0;long startTime = System.currentTimeMillis();FutureTask<Long>[]tasks = new FutureTask[t];for (int i = 0; i < t; i++) {           // 进行任务划分int[] numt = new int[len];for (int j = i * len; j < (i + 1) * len; j++) {numt[j - (i * len)] = nums[j];}// 线程执行FutureTask<Long>task = new FutureTask<Long>(()->{long ans = 0;for (int x: numt) {for (int j = 0; j < 100; j++) {ans += x;}}return ans;});new Thread(task).start();tasks[i] = task;
//            sum += task.get();            // 这个会阻塞线程,所以会慢}for (int i = 0; i < 4; i++) {sum += tasks[i].get();}long endTime = System.currentTimeMillis();System.out.println("使用分治进行" + t + "线程划分执行:");System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");return endTime - startTime;}public static long test4() throws Exception{StartThread startThread = new StartThread();int cnt = 4;                    // 控制线程的个数int sz = MAXN / cnt;            // 每一个线程计算的数量是多少long sum = 0;                        // 计算和是多少long startTime = System.currentTimeMillis();FutureTask<Long>[]tasks = new FutureTask[cnt];for (int i = 0; i < cnt; i++) {FutureTask<Long> task = new FutureTask<Long>(()->{long ans = 0L;int bg = startThread.cur.getAndIncrement();for (int j = bg * sz; j < (bg + 1) * sz; j++) {for (int k = 0; k <100; k++) {ans += nums[j];}}return ans;});new Thread(task).start();tasks[i] = task;}for (int i = 0; i < cnt; i++) {sum += tasks[i].get();}long endTime = System.currentTimeMillis();System.out.println(cnt + "个线程:");System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");return endTime - startTime;}

代码3

  • 测试多少个线程对性能提升最大
  • 结论是逻辑处理器的个数个。
    public static void testNumOfThread() throws Exception{int cnt = 32;long []times =  new long[cnt];for (int i = 1; i < cnt; i++) {times[i] = test4(i);}for (int i = 1; i < cnt; i++) {System.out.println(i + "个线程:" + times[i] + "ms");}}// 可以从第三遍开始,统计一个平均的时间public static void main(String[] args)throws Exception {testNumOfThread();}

总结

  1. task.get()是阻塞的,最好不要放在主线程中,更不要放在线程创建的路径上,最好在开一个线程,进行归并。
  2. 多线程对效率的提升体现在多处理器的并行上。
  3. 这里实现的计算是平均划分数组进行求和,如果不能平均划分就会出错。应该使用归并式的那种划分。
  4. 明天实现一下多线程的归并排序多线程的斐波那契数列

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

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

相关文章

window 服务使用powershell 调用office进行文档内存不够的处理

在项目中为了实现office文件的预览&#xff0c;专门做了个service进行文件的定时转换。 在测试时发现&#xff0c;服务程序 双击执行的时候&#xff0c;文件的转换一切正常&#xff0c;但是当把服务程序安装为服务的时候吗&#xff0c;就会出现如下错误&#xff1a; $PowerPo…

Matlab figure窗口最大化 窗口全屏 图表窗口最大化

我有一个项目&#xff0c;需要把多个数据文件画成的曲线一个个保存为图片&#xff0c;然后再进行集中对比分析。程序运行后&#xff0c;打开目录下保存的图片&#xff0c;发现图片的尺寸都很小&#xff0c;画质也不清晰&#xff0c;后来发现原来matlab显示图片的时候&#xff0…

UCi数据集处理技巧记录

如何起步使用UCI数据集 这里记录一下如何把带分号的数据变成经常使用的csv形式。这里使用wine的例子 https://archive.ics.uci.edu/dataset/186/winequality 原始数据 Wine UCI数据操作 这种带分号的使用python的不好阅读&#xff0c;可以尝试以下步骤&#xff1a; 转变为t…

2023-12-20 LeetCode每日一题(判别首字母缩略词)

2023-12-20每日一题 一、题目编号 2828. 判别首字母缩略词二、题目链接 点击跳转到题目位置 三、题目描述 给你一个字符串数组 words 和一个字符串 s &#xff0c;请你判断 s 是不是 words 的 首字母缩略词 。 如果可以按顺序串联 words 中每个字符串的第一个字符形成字符…

微信小程序-父子页面传值

父子页面传值 父页面向子页面传值 方法一&#xff1a; 父页面&#xff1a; 1. /page/xxx/xxx?id1子页面&#xff1a; onLoad:function(option){ }方法二 <bindtap“func” data-xxx””> 子页面向父页面传值 定义父子页面 父页面&#xff1a;hotspot 子页面&a…

网安面试三十道题(持续更新)

91 mof提权 ## 是mysql的提权方式&#xff0c;在Linux下不能用&#xff0c;就是利用了 c:/windows/system32/wbem/mof/目录下的nullevt.mof文件&#xff0c;每分钟都会在一个特定的时间去执行一次的特征 sql语句&#xff1a; ## 通过shell上传这个文件&#xff0c;通过sql语句写…

惨案后续之---重装python 3.8版本的一系列操作

AssertionError: The environment must specify an action space. 报错 引发的惨案-CSDN博客https://blog.csdn.net/qq_38480311/article/details/135210089 总结&#xff1a; 接上昨日惨案&#xff0c;大意就是 为了解决一个错误&#xff0c;要安装gym0.18.0&#xff0c;经历了…

小信跳房子的题解

原题描述&#xff1a; 时间&#xff1a;1s 空间&#xff1a;256M 题目描述&#xff1a; 小信在玩跳房子游戏&#xff0c;已知跳房子游戏的图表现为一颗完美的具有个节点的二叉树。从根节点依次编号为。节点的左子节点编号为&#xff0c;右子节点编号为。 小信从从节点出发&…

Docker之镜像上传和下载

目录 1.镜像上传 1) 先上百度搜索阿里云 点击以下图片网站 2) 进行登录/注册 3) 使用支付宝...登录 4) 登录后会跳转到首页->点击控制台 5) 点击左上角的三横杠 6) 搜索容器镜像关键词->点击箭头所指 ​ 编辑 7) 进入之后点击实例列表 8) 点击个人实例进入我们的一个…

C++每日一练(8):图像相似度

题目描述 给出两幅相同大小的黑白图像&#xff08;用0-1矩阵&#xff09;表示&#xff0c;求它们的相似度。 说明&#xff1a;若两幅图像在相同位置上的像素点颜色相同&#xff0c;则称它们在该位置具有相同的像素点。两幅图像的相似度定义为相同像素点数占总像素点数的百分比。…

【HarmonyOS】ArkTS语言介绍与组件方式运用

从今天开始&#xff0c;博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”&#xff0c;对于刚接触这项技术的小伙伴在学习鸿蒙开发之前&#xff0c;有必要先了解一下鸿蒙&#xff0c;从你的角度来讲&#xff0c;你认为什么是鸿蒙呢&#xff1f;它出现的意义又是…

设计模式:抽象工厂模式(讲故事易懂)

抽象工厂模式 定义&#xff1a;将有关联关系的系列产品放到一个工厂里&#xff0c;通过该工厂生产一系列产品。 设计模式有三大分类&#xff1a;创建型模式、结构型模式、行为型模式 抽象工厂模式属于创建型模式 上篇 工厂方法模式 提到工厂方法模式中每个工厂只生产一种特定…

NFS的基本使用

#江南的江 #每日鸡汤&#xff1a;岁月匆匆&#xff0c;时光荏苒&#xff0c;感悟人生路漫漫&#xff0c;不忘初心方得始终。 #初心和目标&#xff1a;和从前的自己博弈。 NFS(存储共享服务) 本文要点摘要&#xff1a; 下面将讨论什么是NFS&#xff0c;如何配置NFS&#xff0c;…

全新ui自动化测试框架教学——Cypress

前言 在现阶段自动化测试领域大规模普及的是selenium及appium等常规自动化测试工具&#xff0c;但在其中会有遇到很多影响因素导致测试结果不理想和不准确的情况发生。在经过Darren洋对自动化测试工具调研后&#xff0c;发现了Cypress这一款针对端到端的自动化测试工具&#xf…

52.网游逆向分析与插件开发-游戏反调试功能的实现-检测调试器

码云地址&#xff08;master分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;be9f058bfaaa4b015f2659db842e07ee37e58996 代码下载地址&#xff0c;在 SRO_EX 目录下&#xff0c;文件名为&#xff1a;SRO_Ex检测调试器.z…

认识计算机网络——计算机网络的概念

计算机网络是指将多台计算机通过通信介质连接起来&#xff0c;以便共享资源、交换信息和进行协作的技术体系。在现代社会中&#xff0c;计算机网络已经成为了各个领域的重要基础设施&#xff0c;改变了人们的生活方式和工作方式。本文将介绍计算机网络的基本概念、组成要素和发…

vue3框架笔记

Vue Vue 是一个渐进式的前端开发框架&#xff0c;很容易上手。Vue 目前的版本是 3.x&#xff0c;但是公司中也有很多使用的是 Vue2。Vue3 的 API 可以向下兼容 2&#xff0c;Vue3 中新增了很多新的写法。我们课程主要以 Vue3 为主 官网 我们学习 Vue 需要转变思想&#xff0…

Springboot整合JSP-修订版本(Springboot3.1.6+IDEA2022版本)

1、问题概述&#xff1f; Springboot对Thymeleaf支持的要更好一些&#xff0c;Springboot内嵌的Tomcat默认是没有JSP引擎&#xff0c;不支持直接使用JSP模板引擎。这个时候我们需要自己配置使用。 2、Springboot整合使用JSP过程 现在很多的IDEA版本即使创建的项目类型是WAR工…

kivy BoxLayout说明

BoxLayout的特点 自动排列&#xff1a;BoxLayout会根据其orientation属性&#xff08;垂直或水平&#xff09;自动排列其子部件。这简化了布局的过程&#xff0c;尤其是当你有许多需要按顺序排列的部件时。可定制的间距和对齐&#xff1a;通过spacing属性&#xff0c;你可以控…

QT应用篇 三、QML自定义显示SpinBox的加减按键图片及显示值效果

QT应用篇 一、QT上位机串口编程 二、QML用Image组件实现Progress Bar 的效果 三、QML自定义显示SpinBox的加减按键图片及显示值效果 文章目录 QT应用篇前言一、qml需求二、使用组件1.SpinBox组件2.SpinBox中QML的使用 总结 前言 记录自己学习QML的一些小技巧方便日后查找 QT的…