多线程的代码案例
目录
单例模式
饿汉模式
懒汉模式
阻塞队列
生产者消费者模型意义:
阻塞队列使用方法
实现阻塞队列
阻塞队列实现生产者消费者模型
定时器
实现简单的定时器
工厂模式
线程池
为啥呢? 从池子里面取 比 创建线程 效率更高
线程池的创建
怎么填坑
ThreadPoolExecutor
线程数目设置
实现线程池
小结
两个设计模式: 单例模式, 工厂模式
单例模式
有些场景中希望有些类仅仅创建一个对象, 代码中很多管理数据的对象都是单例的, MySQL JDBC等.
人可能会出错, 需要编译器帮我们做出监督. 就比如 @Override 必须是方法重写.,在语法层面上没有对单例做出支持, 只能通过编程技巧实现
饿汉模式
刚开始就创建了实例举个例子:
//期望这个类能有唯一实例
class Singleton {//设置为静态变量在 Singleton 类被加载时会创建实例private static Singleton instance = new Singleton();//获取实例public static Singleton getInstance() {return instance;}//把构造方法设为 私有 , 类外面的代码无法 new 出类对象了.private Singleton() {};
}
注意:
1> 在类的内部提供线程的实例
2> 把构造方法设为 private ,避免其他代码创建实例.
懒汉模式
先判断是否需要创建实例举个例子:
//期望这个类能有唯一实例
class SingletonLazy {private static volatile SingletonLazy instance = null;//获取实例public static SingletonLazy getInstance() {if(instance == null) {synchronized (SingletonLazy.class) {if(instance == null) {instance = new SingletonLazy();}}}return instance;}//把构造方法设为 私有 , 类外面的代码无法 new 出类对象了.private SingletonLazy() {};
}
注意:
1> 第一次判断是否为空原因:
因为加锁开销很大, 而且可能涉及到锁冲突, 所以我们增加一次判断, 不为空直接返回 instance
2> 加锁的原因:
在本操作中会出现读取和修改的操作, 会出现两个都判断为空后创建多个实例的情况.
3> 使用 volatile 原因:
指令重排序问题
编译器为了提高效率, 可能调整代码的执行顺序, 但是必须保持代码逻辑不变, 单线程没问题, 但是多线程可能有问题.
new 操作, 可能触发指令重排序
new 操作分为三步:
1. 申请内存空间
2. 在内存空间上构造对象
3. 把内存地址给 instance
可能按照 123, 132顺序执行, 1一定先执行
在多线程下, 假设 t1线程 按照1 3 2 的顺序 执行1 3后, instance非空指向一个没初始化的非法对象, 这时 t2线程 在判断instance 不为空后, 直接返回一个非法对象, 导致出现bug
使用 volatile 保证不会出现指令重排序问题
阻塞队列
多线程代码中比较常用到的一种数据结构
特殊的队列
1> 线程安全
2> 带有阻塞特性
a) 如果队列为空, 继续出队列, 就会发生阻塞, 阻塞到其他线程往队列里添加元素位置为止
b) 如果队列为满, 继续入队列, 也会发生阻塞, 阻塞到其他线程从队列中取走元素位置为止.
意义: 实现 " 生产者消费者模型 " 一种常见的多线程代码编写方式
举个例子: 包饺子
1> 每个人分别负责擀饺子皮和包饺子
2> 当擀饺子皮快了 就会在 放饺子皮的盖帘满的时候停下来等包饺子的
3> 当包饺子快了 就会停下来等 擀饺子皮的
盖帘就相当于阻塞队列
生产者 把生产出来的内容放到阻塞队列中
消费者 从阻塞队列中获取元素
生产者消费者模型意义:
1> 解耦合
两个模块联系越紧密, 耦合就越高, 这个模型让耦合降低
2> 削峰填谷
服务器 A 给服务器 B发起请求, 不同服务器消耗的硬件资源不一样, A收到的请求发给B可能就挂了.使用削峰填谷让 B 接受的请求按照 B 的原有节奏处理情况.(这种情况一般不会持续存在, 就好比学校抢课的情况), 峰值过后 B把积压的数据处理掉
阻塞队列使用方法
在 Java 标准库里, 已经提供了现成的 阻塞队列直接使用
在标准库里, 针对 BlockingQueue 提供了两种最重要的实现方式
1> 基于数组
2> 基于链表
BlockingQueue 一般不适用 Queue 中的一些方法, 因为他们不具备阻塞的特性.
一般使用 (put 阻塞式的入队列), (take 阻塞式的出队列)
示例:
public class Test {public static void main(String[] args) throws InterruptedException {BlockingDeque<String> queue = new LinkedBlockingDeque<>();queue.put("111");queue.put("222");queue.put("333");queue.put("444");String elem = queue.take();System.out.println(elem);elem = queue.take();System.out.println(elem);elem = queue.take();System.out.println(elem);elem = queue.take();System.out.println(elem);elem = queue.take();System.out.println(elem);}
}
最后一次输出时发生了阻塞.
实现阻塞队列
基于普通队列加上阻塞和线程安全
普通队列基于数组 或者 基于链表
基于数组实现队列理解成一个环
class MyBlockingQueue {private String[] data = new String[1000];// 队列的起始位置private int head = 0;// 队列的结束位置的下一个位置private int tail = 0;//队列中有效元素的个数private int size = 0;//提供的方法 入队列 出队列public void put(String elem) throws InterruptedException {synchronized (this) {while(size == data.length) {this.wait();}data[size] = elem;tail++;if(tail == data.length) {tail = 0;}size++;//这个 notify 用来唤醒 take 中的 waitthis.notify();}}public String take() throws InterruptedException {synchronized (this) {while(size == 0) {this.wait();}String ret = data[head];head++;if(head == data.length) {head = 0;}size--;//这个 notify 用来唤醒 put 中的 waitthis.notify();return ret;}}
}
wait 除了可以用 notify 唤醒, 还可以用 interrupt 唤醒, 直接整个方法结束了, 因为使用了 throws 抛出异常, 这是没有什么事
如果使用 try catch 方式就会出现bug, 让 tail 把指向的元素覆盖掉了, 然后弄丢了一个元素, 而且 size 也会比数组最长长度还大.(此处不理解看http://t.csdnimg.cn/OBwXN -->中断一个线程目录)
所以在wait 返回的时候进一步确认是否当前队列是满的不是, 如果是满的继续进行wait
所以直接使用 while 判定是否是满的.
为了避免内存可见性问题, 把 volatile 加好
阻塞队列实现生产者消费者模型
package Demo2;import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;class MyBlockingQueue {private String[] data = new String[1000];// 队列的起始位置private volatile int head = 0;// 队列的结束位置的下一个位置private volatile int tail = 0;//队列中有效元素的个数private volatile int size = 0;//提供的方法 入队列 出队列public void put(String elem) throws InterruptedException {synchronized (this) {while(size == data.length) {this.wait();}data[tail] = elem;tail++;if(tail == data.length) {tail = 0;}size++;//这个 notify 用来唤醒 take 中的 waitthis.notify();}}public String take() throws InterruptedException {synchronized (this) {while(size == 0) {this.wait();}String ret = data[head];head++;if(head == data.length) {head = 0;}size--;//这个 notify 用来唤醒 put 中的 waitthis.notify();return ret;}}
}public class Test {public static void main(String[] args) {MyBlockingQueue queue = new MyBlockingQueue();// 消费者Thread t1 = new Thread(() -> {while(true) {try {String result = queue.take();System.out.println("消费元素: " + result);Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 生产者Thread t2 = new Thread(() -> {int num = 1;while(true) {try {queue.put(num+ " ");System.out.println("生产元素: " + num);num++;} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}
定时器
约定一个时间, 时间到达之后执行某个代码逻辑, 在网络通信中很常见
在 标准库 中有现成定时器的实现
public static void main(String[] args) {Timer timer = new Timer();// 给定时器安排了一个任务, 预定在 xxx 时间去执行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器任务");}}, 2000);System.out.println("程序启动!");}
使用匿名内部类的写法继承 TimerTask 创建出实例, 目的时重写 run, 描述任务的详细情况
当前代码也是多线程, timer 里面包含一个线程, 下图是运行结果
可以发现整个进程没有结束, 因为 Timer 内部的线程阻止了进程结束.
Timer 里面可以安排多个任务.
public static void main(String[] args) {Timer timer = new Timer();// 给定时器安排了一个任务, 预定在 xxx 时间去执行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3000");}}, 3000);System.out.println("程序启动!");timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000");}}, 2000);System.out.println("程序启动!");timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000");}}, 2000);System.out.println("程序启动!");}
实现简单的定时器
1> Timer 中需要有一个线程, 扫描任务是否到时间了, 可以执行了
2> 需要一个数据结构把所有任务保存起来(使用优先级队列)
3> 创建一个类, 通过类的对象描述一个任务(至少要包含任务内容和时间)
其中需要记录, 绝对的时间.
import java.awt.*;
import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;// 通过这个类, 描述一个任务
class MyTimerTask implements Comparable<MyTimerTask> {// 执行的任务private Runnable runnable;// 执行任务的时间private long time;// 此处的 delay 就是 schedule 方法传入的 "相对时间"public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;}@Overridepublic int compareTo(MyTimerTask o) {// 让队首元素是最小时间的值return (int) (this.time - o.time);// 让队首元素是最大时间的值//return (int) (o.time - this.time);}public long getTime() {return time;}public Runnable getRunnable() {return runnable;}
}// 自己的定时器
// 添加元素和扫描线程是不同线程操作同一个队列, 需要加锁 <--原因之一
class MyTimer {// 使用一个数据结构, 保存所有的任务private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 使用这个对象作为锁对象private Object locker = new Object();public void schedule(Runnable runnable, long delay) {synchronized(locker) {queue.offer(new MyTimerTask(runnable, delay));locker.notify();}}// 扫描线程public MyTimer() {// 创建一个扫描线程Thread t = new Thread(() -> {// 扫描线程需要不停扫描看是否到达时间while (true) {try {synchronized (locker) {// 不要使用 if 作为 wait 的判定条件, 应使用while// 使用 while 是为了在唤醒之后 在再次确认一下条件while (queue.isEmpty()) {locker.wait();}MyTimerTask task = queue.peek();// 比较一下当前的队首元素是否可以执行了long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) {// 执行任务task.getRunnable().run();//执行完了, 就从队列中删除queue.poll();} else {// 不可执行, 先等着, 等待下一轮的循环判定locker.wait(task.getTime() - curTime);}}}catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}public class Demo2 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("3000");}}, 3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("2000");}}, 2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("1000");}}, 1000);}
}
工厂模式
线程池
线程创建/销毁 比 进程快, 但是进一步提高创建/销毁的频率, 线程的开销也不能忽视了
两种提高效率的方法:
1> 协程 (轻量级线程)
相对于线程, 把系统调度的过程给忽略了,(程序猿手动调度), 当下比较流行(Java 标准库没有协程)
2> 线程池
兜底, 使线程不至于很慢
例子: 我是个妹子, 在谈男朋友, 一段时间后, 我不想和他好了, 就冷暴力然后分手, 分手之后再去找另一个小哥哥, 然后和另一个小哥哥好上了.
线程池就是我在谈第一个男朋友的时候就同时和其他小哥哥搞暧昧(培养感情), 哪天想分手了直接分, 然后无缝衔接
线程池: 在使用第一个线程的时候, 提前把 2, 3, 4, 5线程创建好(培养感情), 后续想使用新的线程不必创建, 直接使用(创建线程的开销降低了)
为啥呢? 从池子里面取 比 创建线程 效率更高
从池子里取, 就是纯粹用户态操作
创建新的线程需要 用户态 + 内核态 相互配合 完成
操作系统是由 内核 + 配套的应用程序 构成
内核 是系统最核心的部分, 创建线程操作需要调用系统 api, 进入到内核中, 按照内核态的方式来完成一系列动作
当你想要创建线程的时候, 内核需要给所有进程提供服务, 不可控, 难以避免会做一些其他的事导致效率减低
线程池的创建
Java标准库提供了写好的线程池.
创建线程池对象并没有 new , 而是通过专门的方法返回了一个线程池对象(工厂模式), 通常创建对象使用 new , new 就会触发类的构造方法, 但构造方法存在一定的局限性. 工厂模式是给构造方法填坑的.
怎么填坑
我们构造一个对象希望有多种构造方式, 这就需要多个构造方法, 但是构造方法的名字必须是类名, 不同的构造方法只能通过 重载区分, 但是如果实现方法不一样, 但是参数类型/个数一样咋办呢?
使用工厂设计模式, 使用普通的方法代替构造方法完成初始化工作, 普通方法使用名字区分.
Executors 是一个 工厂类, newCachedThreadPool 是工厂方法, 使用静态方法通过类名调用
工厂方法有很多, 上述方法创建出来的线程池对象的线程数目可以动态适应, 随着王线程池里面添加任务, 线程池中的线程自动创建, 创建出来在池子里保留一定时间以备后续使用.
这个方法是固定的线程池, 调用方法时手动指定创建几个线程
还用很多其他线程池上面介绍的两种用的更多一点
ThreadPoolExecutor
上述工厂方法生成的线程池本质上是对 类(ThreadPoolExecutor) 的封装
核心方法:
1> 添加任务
2> 构造
举例: 1> 添加任务 (简单)
使用 submit 把任务交给线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Demo3 {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(4);service.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});}
}
2> 构造方法 (重点)
构造方法中参数很多[经典面试题]
在 juc 包里面, 并发编程相关内容
全部参数 如下图:
对这 4 种情况 举个例子:
我有 任务 A 要做, 朋友来让我帮忙做任务 B, 这时我有 4 种回应方法.
1> 我心态崩了, 大哭. 抛出异常
2> 我对朋友说你自己做, 朋友自己做任务 B
3> 我的任务 A 不做了, 就去帮朋友
4> 我直接拒绝帮忙, 我仍然做任务 A , 朋友也不做任务 B 了
线程数目设置
使用线程池需要设置线程的数目, 设置多少合适?
具体数目是不对的, 需要实际情况分析
原因:
一个线程执行代码主要有两类:
1> cpu 密集型: 代码主要是进行 算术运算/逻辑判断
2> IO密集型: 代码里主要进行的是 IO 操作
如果是 1> 这个时候线程池的数量不要超过 N (设 N 就是极限), 比 N 更大, 就无法提高效率了, cpu吃满了, 线程越多反而增加调度的开销
如果是 2> 不吃 CPU, 此时设置的线程数可以超过 N, 一个核心可以通过调度的方式来并发执行.
实现线程池
class MyThreaPool {// 任务队列private BlockingDeque<Runnable> queue = new ArrayBlockingQueue<>();// 通过这个方法, 把任务添加到队列中public void submit(Runnable runnable) throws InterruptedException {//此处策略是第 5 种, 拒绝策略, 阻塞等待queue.offer(runnable);}public MyThreaPool(int n) {// 创建出 n 个线程, 负责执行上述队列中的任务for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {// 让这个线程从队列中消费任务,并进行执行try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {e.printStackTrace();}});t.start();}}
}
小结
认真学习各种多线程代码实例, 理解其中的含义, 将各个代码的的易错点分析透彻
相关文章:

多线程的代码案例
目录 单例模式 饿汉模式 懒汉模式 阻塞队列 生产者消费者模型意义: 阻塞队列使用方法 实现阻塞队列 阻塞队列实现生产者消费者模型 定时器 实现简单的定时器 工厂模式 线程池 为啥呢? 从池子里面取 比 创建线程 效率更高 线程池的创建 怎么填坑 ThreadPoolExec…...

什么是Java中的设计模式?请列举几种常见的设计模式
一、引言 在软件开发中,设计模式是解决特定设计问题的最佳实践或通用解决方案。Java作为一种广泛使用的编程语言,其设计模式在软件设计和架构中起着至关重要的作用。设计模式不仅提高了代码的可读性和可维护性,还使得代码更加灵活和可扩展。…...

绘制奇迹:Processing中的动态图形与动画
🚀 欢迎回到Processing的世界,你的艺术编程航程刚刚开始。在我们的入门篇中,你已经学会了如何用Processing绘制基本的静态图形。现在,让我们一起探索Processing强大的动态图形和动画功能,释放你的创造力,走…...

Django视图Views
Views视图 HttpRequest 和HttpResponse Django中的视图主要用来接受web请求,并做出响应。视图的本质就是一个Python中的函数视图的响应分为两大类 1)以Json数据形式返回(JsonResponse) 2)以网页的形式返回 2.1)重定向到另一个网页 (HttpRe…...

国内智能搜索工具实战教程
大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…...

WebSocket or SSE?即时通讯的应用策略【送源码】
最近在研究H5推送,发现除了我们常用的WebSocket以外,其实还有一种协议也能实现H5推送,那就是SSE协议。 而且,当前主流的大模型平台,比如ChatGPT、通义千问、文心一言,对话时采用的就是SSE。 什么是SSE协议…...

QT实现Home框架的两种方式
在触摸屏开发QT界面一般都是一个Home页面,然后button触发进入子页面显示,下面介绍这个home框架实现的两种方式: 1.方式一:用stackedWidget实现 (1)StackedWidget控件在Qt框架中是一个用于管理多个子窗口或…...

机器学习笔记03
1.线性回归(linear regression) 是利用回归方程(函数)对一个或者多个自变量(特征值)和因变量(目标值)之间关系进行建模的一种分析方法。 线性模型: 1.线性关系࿱…...

【全面介绍下Spring】
🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共…...

MYSQL-存储引擎
存储引擎就是储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被 称为表类型。 存储引擎特点 . InnoDB 介绍 InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在MySQL 5.5之后,InnoDB是默认的MySQL存储引擎。 >特…...

红蓝对抗 网络安全 网络安全红蓝对抗演练
什么是红蓝对抗 在军事领域,演习是专指军队进行大规模的实兵演习,演习中通常分为红军、蓝军,演习多以红军守、蓝军进攻为主。类似于军事领域的红蓝军对抗,网络安全中,红蓝军对抗则是一方扮演黑客(蓝军&…...

springboot 序列化和反序列化
介绍 在Java中,序列化和反序列化是一种将对象转换为字节流或将字节流转换为对象的机制。通过序列化,可以将对象存储到文件中、传输到网络上,或者在分布式系统中进行对象的传递。本文将详细介绍Java序列化和反序列化的原理、使用方法和常见应用…...

德克萨斯大学奥斯汀分校自然语言处理硕士课程汉化版(第一周) - 自然语言处理介绍和线性分类
自然语言处理介绍和线性分类 1. 自然语言处理介绍2. 线性二分类3. 情感分析和基础特征提取 3.1. 情感分析3.2. 特征提取3.3. 文本预处理 4. 学习的基础-梯度下降算法5. 感知机6. 逻辑回归7. 情感分析8. 感知机和逻辑回归 1. 自然语言处理介绍 自然语言处理的目标是什么 能够解…...

SQL注入漏洞常用绕过方法
SQL注入漏洞 漏洞描述 Web 程序代码中对于用户提交的参数未做过滤就直接放到 SQL 语句中执行,导致参数中的特殊字符打破了原有的SQL 语句逻辑,黑客可以利用该漏洞执行任意 SQL 语句,如查询数据、下载数据、写入webshell 、执行系统命令以及…...

C语言输出符
C语言输出符 以下是C语言中一些常用的格式化输出的格式控制符及其对应的数据类型: 格式控制符描述对应数据类型%d十进制有符号整数int%ld长整型long int%lld长长整型long long int%u十进制无符号整数unsigned int%lu无符号长整型unsigned long int%llu无符号长长整…...

申请一个开发者域名
申请一个开发者域名 教程 fourm.js.org 因本地没安装 hexo 环境,模板下载的 html...

接搭建仿美团、代付系统源码搭建教程
最近很多粉丝催更、分享一下地球号:xiaobao0214520(WX) 现在大家都很流行搞网恋,我们搭建一个跟美团相似的系统 然后开发一个好友代付,我们在点单的时候转发链接让网恋对象付钱 若只是单点外卖的话,能榨出的油水还是太少。 所以…...

迭代的难题:敏捷团队每次都有未完成的工作,如何破解?
各位是否遇到过类似的情况:每次迭代结束后,团队都有未完成的任务,很少有完成迭代全部的工作,相反,总是将上期未完成的任务重新挪到本期计划会中,重新规划。敏捷的核心之一是“快速迭代,及时反馈…...

ChatGPT未来可能应用于iPhone?
苹果接即将与OpenAI达成协议 ChatGPT未来应用于iPhone 前言 就在5月11日,苹果公司正与OpenAI进行深入讨论,计划在其最新的iOS操作系统中整合OpenAI的先进技术。这一举措是苹果公司在为其产品线融入更先进的人工智能功能所做努力的一部分。 目前情况双方…...

Spring之bean的细节(创建方式、作用范围、生命周期)
在Spring框架中,Bean是一个非常重要的概念,它代表了应用程序中需要被管理的对象。关于Bean的细节,我们可以从创建方式、作用范围以及生命周期三个方面进行阐述。 创建方式 Spring支持以下三种方式创建Bean: 调用构造器创建Bean…...

探索STLport:C++标准模板库的开源实现
在C++编程的世界里,STL(标准模板库)是一个不可或缺的工具。它提供了许多用于数据结构、算法和其他重要功能的模板类和函数。然而,标准模板库的实现并非只有一种,而其中一个备受推崇的选择就是STLport。 官方下载: STLport: Welcome! STLport是什么? STLport是一个开…...

计算机Java项目|Springboot高校心理教育辅导设计与实现
作者主页:编程指南针 作者简介:Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容:Java项目、Python项目、前端项目、人工智能与大数据、简…...

数据结构简单介绍、算法简单介绍、算法复杂度、时间复杂度等的介绍
文章目录 前言一、什么是数据结构二、什么是算法三、算法复杂度1. 时间复杂度① 时间复杂度的定义② 大O的渐进表示法 总结 前言 数据结构简单介绍、算法简单介绍、算法复杂度、时间复杂度等的介绍 一、什么是数据结构 数据结构是计算机存储,组织数据结构的方式&…...

Google I/O 2024:有关AI的一切已公布|TodayAI
2024年谷歌I/O大会圆满落幕,谷歌在会上发布了一系列更新,涵盖从最新的人工智能技术到Android系统的多项改进。此次大会特别关注于谷歌的Gemini人工智能模型,并详细介绍了这些模型如何被融入到Workspace、Chrome等多个应用程序中,展…...

【Shell脚本】Shell编程之数组
目录 一.数组 1.基本概念 2.定义数组的方法 2.1.方法一 2.2.方法二 2.3.方法三 2.4.方法四 2.5.查看数组长度 2.6.查看数组元素下标 3.数组分片 4.数组字符替换 4.1.临时替换 4.2.永久替换 5.数组删除 5.1.删除某个下标 5.2.删除整组 6.数组遍历和重新定义 7…...

Python 全栈系列246 任务调度对象WFlaskAPS
说明 之前已经完全跑通了任务调度,实现了S2S的流转Python 全栈系列243 S2S flask_celery。由于request请求用起来比较别扭,所以创建一个对象来进行便捷操作。 内容 1 功能 WFlaskAPS包含管理定时任务的必要功能 from datetime import datetime from…...

关于Windows中的NTUSER.DAT文件的知识,看这篇文章就差不多了
每个用户配置文件中都隐藏着一个名为NTUSER.DAT的文件。此文件包含每个用户的设置和首选项,因此你不应该删除它,也可能不应该编辑它。Windows会自动为你加载、更改和保存该文件。 NTUSER.DAT包含你的用户配置文件设置 每次更改Windows和已安装程序的外观和行为时,无论是桌…...

【Linux】动态库与静态库的底层比较
送给大家一句话: 人生最遗憾的,莫过于,轻易地放弃了不该放弃的,固执地坚持了不该坚持的。 – 柏拉图 (x(x_(x_x(O_o)x_x)_x)x) (x(x_(x_x(O_o)x_x)_x)x) (x(x_(x_x(O_o)x_x)_x)x) 底层比较 1 前言2 编译使用比较2 如何加载Than…...

私活更好用:SpringBoot开源项目!!【送源码】
今天分享一款非常香的SpringBoot大屏开源项目,非常适合接私活用。 这是一款基于SpringBoot代码生成器的快速开发平台!采用前后端分离架构:SpringBoot,Mybatis,Shiro,JWT,Vue&Ant Design。强…...

SprintBoot案例-增删改查
黑马程序员JavaWeb开发教程 文章目录 一、准备工作1. 准备数据库表1.1 新建数据库mytlias1.2 新建部门表dept1.3 新建员工表emp 2. 准备一个Springboot工程2.1 新建一个项目 3. 配置文件application.properties中引入mybatis的配置信息,准备对应的实体类3.1 引入myb…...