JavaEE 【知识改变命运】05 多线程(4)
文章目录
- 单例模式
- 什么是单例模式
- 饿汉模式
- 懒汉模式
- 多线程- 懒汉模式
- 分析多线程问题
- 第一种添加sychronized的方式
- 第二种添加sychronized的方式
- 改进第二种添加sychronized的方式(DCL检查锁)
- 阻塞队列
- 什么是阻塞队列
- 什么是消费生产者模型
- 标准库中的阻塞队列
- 消息队列应用的场景
- 自己模拟实现阻塞队列
- 定时器
- 标准库中的定时器
- 实现定时器
- 工厂模式
- 线程池
- 线程池的一些问题
- 实现一个线程池
- 创建系统自带的线程池
- wait和sleep的区别
单例模式
什么是单例模式
- 单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建出多个实例
- 单例模式实现方式很多,最常用饿汉模式和懒汉模式实现
饿汉模式
- 创建过程:
– 1. 定义一个static修饰的变量,就可以包子这个变量全局唯一
– 2.构造方法私有化,防止变量被修改
– 3.提供一个获取变量的get静态方法,通过类名的方式去调用
public class Singleton {//懒汉模式//创建一个私有静态属性,并且把对象new出来private static Singleton instance =new Singleton();//私有化构造器private Singleton() {}//提供一个公共的静态方法,返回单例对象public static Singleton getInstance() {return instance;}public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2); // true}
}
- 把这种类加载时候就完成对象的初始化的创建方式,就叫”饿汉模式“
- 这种模式存在的问题是,可能对象创建了但是没有使用,从而导致资源浪费。
懒汉模式
public class SingLetonLazy {//创建一个对象不去new对象private static SingLetonLazy instance;//私有化构造器private SingLetonLazy() {}//提供一个公共的静态方法,返回单例对象public static SingLetonLazy getInstance() {if(instance==null) {instance=new SingLetonLazy();}return instance;}public static void main(String[] args) {SingLetonLazy s1 = SingLetonLazy.getInstance();SingLetonLazy s2 = SingLetonLazy.getInstance();System.out.println(s1 == s2); // true}
}
- 懒汉模式创建对象,在要获得单例对象的时候,创建,避免了资源的浪费但是存在多线程安全问题。
多线程- 懒汉模式
public class SingLetonLazy {private static SingLetonLazy instance;//私有化构造器private SingLetonLazy() {}//提供一个公共的静态方法,返回单例对象public static SingLetonLazy getInstance() {if(instance==null) {instance=new SingLetonLazy();}return instance;}public static void main(String[] args) {for (int i = 0; i < 10; i++) {Thread t1 =new Thread(()->{try {sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}SingLetonLazy s1 = SingLetonLazy.getInstance();System.out.println(s1);});t1.start();}}
}
出现了多线程问题。
分析多线程问题
第一种添加sychronized的方式
public static SingLetonLazy getInstance() {if(instance==null) {synchronized (SingLetonLazy.class){instance=new SingLetonLazy();}}return instance;}
- 这种写法不能保证多线程安全
第二种添加sychronized的方式
public static SingLetonLazy getInstance() {synchronized (SingLetonLazy.class){if(instance==null) {instance=new SingLetonLazy();}}return instance;}
- 这种写法似乎可以保证多线程安全,但是还是存在一个问题,当一个线程进行这个方法,如果没有初始化,则获取锁进行初始化操作,此时单例对象被第一个线程创建完成,后面的线程以后永远不会在执行new对象的操作,synchronized就没必要添加了,第二次线程开始这个加锁解锁都是无效的操作,lock和unlock对应的锁指令是互斥锁,比较消耗系统资源。
- 添加锁本质就会消耗很多资源
改进第二种添加sychronized的方式(DCL检查锁)
private volatile static SingLetonLazy instance;//给共享变量加上volatile
public static SingLetonLazy getInstance() {//第一次判断是否加锁if(instance==null) {synchronized (SingLetonLazy.class) {判断是否创建了一个对象if (instance == null) {instance = new SingLetonLazy();}}}return instance;}
阻塞队列
什么是阻塞队列
- 阻塞队列本质还是队列,遵循”先进先出“的原则
- 阻塞队列是一种线程安全的数据结构,有以下特征
– 当队列满的时候,继续入队就会发生阻塞等待,直到队列中有其他线程取出元素后,队列有空位才会再次入队
– 当队列空的时候,继续出队就会放生阻塞等待,知道队列中有其他线程插入元素时候,队列有元素才会再次出队 - 阻塞队列适用于一种典型场景‘消费生产者模型’
什么是消费生产者模型
- 生产者消费者模式就是通过一个容器解决消费者和生产者的强耦合问题。
- 生产者和消费者不会直接影响,生产者生产的资源直接放入容器(阻塞队列)中,消费者消费的资源,直接从容器(阻塞队列)中拿。从而保证生产者不会生产资源等待消费者消费,消费者也不会等待生产者生产资源。
- 阻塞队列相当于一个缓冲区,平衡生产者和消费者的处理能力
– 比如双11时候,会涌入大量的支付订单,这时候如果服务器直接处理这些订单,可能就会把服务器挤爆,这时候中间设置一个阻塞队列,把产生的大小支付订单扔进阻塞队列里面,然后服务器根据自己的处理能力,从队列里面取出要处理的订单,从而达到削峰的效果,防止服务器被挤爆。 - 阻塞队列也能使生产者和消费者之间 解耦
– 过年期间大家都会包饺子,擀饺子皮相当于生产者,包饺子相当于消费者,中间放个案板,所有的饺子皮都放在案板上,包饺子皮的人直接从案板上取,擀饺子皮的可能是妈妈可能是爸爸可能是我,无论是谁擀饺子皮消费者都不关心,因为都是从案板上取的饺子皮。
标准库中的阻塞队列
- 在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.
– BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
– put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
– BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性. - 创建一个BlockingQueue
– 其中capacity是这个队列的大小。
– 这里设置一个三个大小的阻塞队列,当第四个元素入队时候就会发生阻塞等待
– 这里取出三个元素后,队列为空,队列阻塞等待
– put和take都会抛出一个InterrupteException异常
- 其他补充常问的方法
– add()
– offer()
– remove()
– poll
消息队列应用的场景
-
解耦
– 高内聚,低耦合:业务强相关的代码组织在一起,不相关的单独定义便于以后的维护,以为要把重复的代码尽量抽象出来,封装成一个公共方法,在需要的地方直接调用这个方法即可
– 生产消息的应用程序把消息写进消息队列(生产者),使用消息的应用程序从消息队列里面取出消息(消费者)
在这个模型中,服务器A要时刻感应到服务器B,在调用的过程中双方都要知道对方需要调用的参数和调用方式
,在ABC整个调用的链路中秒如果其中一个出现了问题,就会影响整个业务执行
-
削峰填谷(流量)
– 针对流量暴增的时候使用消息队列来进行缓冲
– 实例:
-
异步操作
周末:我和我女朋友取买包子
- 同步操作:她一直等我买包子回来,开始,中间这个过程啥也不干,同步发出请求后,必须要等待响应才能- 进行下一步操作
- 异步操作:她让我去之后,在家做点别的事情,比如,做点小菜,熬点稀饭,异步操作,发出请求之后,不需要等待响应,而做其他的事情,等待响应主动通知自己
自己模拟实现阻塞队列
public class MyBlockingDeque {int [] arr;volatile int head=0;volatile int tail=0;volatile int size=0;MyBlockingDeque(int capacity){if(capacity<=0) {throw new RuntimeException("capacity must be positive");}arr = new int[capacity];}public void put(int val) throws InterruptedException {while(size>=arr.length) {synchronized (this){this.wait();}}arr[tail]=val;tail++;if(tail>=arr.length) {tail=0;}size++;synchronized (this){this.notifyAll();}}public synchronized int take() throws InterruptedException {while(size==0) {this.wait();}int val =arr[head];head++;if(head>=arr.length) {head=0;}size--;this.notifyAll();return val;}
}
class Main{public static void main(String[] args) throws InterruptedException {MyBlockingDeque myBlockingDeque = new MyBlockingDeque(10);int i=0;new Thread(()->{while (true){try {sleep(1000);int val = myBlockingDeque.take();System.out.println(Thread.currentThread().getName()+"取出成功"+val);} catch (InterruptedException e) {e.printStackTrace();}}}).start();while (true){myBlockingDeque.put(i);System.out.println(Thread.currentThread().getName()+"添加成功"+i);i++;}}
}
- put时候
- take时候
- 我们上锁可以锁代码块也可以方法
- if改为while的原因是防止大量现场
定时器
标准库中的定时器
- 标准库中定义一个TImer类。Timer类的核心方法为schedule
- schedule包含两个参数,第一个参数指定要执行的代码任务,第二个参数指定多场实际之后执行。
import java.util.Timer;
import java.util.TimerTask;public class Demo_801 {public static void main(String[] args) {// 使用jdk中提供的类,创建一个定器Timer timer = new Timer();//向定时器添加任务timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("Hello World!");}},1000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("任务1");}},1500);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("任务2");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("任务3");}},2500);}
}
ctrl+p查看方法的参数列表
定义自己的任务
延迟多久执行的任务
任务具体执行的时间
实现定时器
- 设计思路
- 用一个类描述任务和执行任务的时间
– 具体任务逻辑用Runable表示,执行时间可以用一个long型delay表示 - 组织任务和时间对应的对象
– 可以考虑用一个阻塞队列,我们选择用PriorityBlockingQueue(),保证扫描任务时候,延时最少的任务先执行
-
提供一个方法,提交任务
-
要有一个线程执行任务
– 在哪里定义扫描线程?
–在构造方法里面直接定义线程
– 1.取出队首元素,2.判断一下任务到执行的时间没有,3,如果到了就执行,4.没有就放回队列
- 代码实现
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;public class MyTimer {//用一个阻塞队列来组织任务private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();private Object lock = new Object();public MyTimer() {//创建线程Thread thread =new Thread(()->{while(true){try {//从队列中取出任务MyTask task=this.queue.take();//判断有没有到执行的时间long currentTime=System.currentTimeMillis();if(currentTime>=task.getTime()){//时间到了执行task.getRunnable().run();} else{//时间没到,将任务放回队列中this.queue.put(task);}} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();}/*** 添加定时任务* @param runnable 任务* @param delay 延时* @throws InterruptedException*/public void schedule(Runnable runnable,long delay) throws InterruptedException {//根据传的参数构造一个MyTask对象MyTask task=new MyTask(runnable,delay);//将这个MyTask对象阻塞放入队列中queue.put(task);}
}//MyTask类,用于封装任务和执行时间
class MyTask implements Comparable<MyTask>{//任务private Runnable runnable;//执行时间private long time;public MyTask(Runnable runnable, long delay) {//增强代码健壮性if(runnable==null){throw new IllegalArgumentException("任务不能为空");}if(delay<0) {throw new IllegalArgumentException("延迟时间不能为负数");}this.runnable = runnable;//计算出任务的执行时间this.time = delay+System.currentTimeMillis();}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {if(this.getTime()<o.getTime()){return -1;} else if(this.getTime()==o.getTime()){return 0;}else {return 1;}//万一时间超过了long的范围溢出,怎么办?用上面的比较比较好//return (int)(this.getTime()-o.getTime());}
}
public class Test {public static void main(String[] args) throws InterruptedException {MyTimer timer = new MyTimer();timer.schedule(new Runnable(){@Overridepublic void run() {System.out.println("任务1");}},1000);timer.schedule(new Runnable(){@Overridepublic void run() {System.out.println("任务2");}},500);timer.schedule(new Runnable(){@Overridepublic void run() {System.out.println("任务3");}},2000);//timer.schedule(null,-100);//任务加强健壮性}
}
- 注意事项:
– 注意我们要实现Conparable接口指定排序规则
– 我们要添加校验,防止非法的输入
– 解决数据可能会溢出的问题,比如设置的时间
- 再次深度优化我们代码
- 以上代码我们存在“忙等”的情况
- 优化后的代码
– 这里注意一下这个lambda表达式中的this引用的是他所在对象的实例。 - 新的问题:当任务1在等待时候,这时候如果又put进来一个新的任务,这个等待的时间就有问题。再次优化
每添加新的任务都进行一次唤醒,保证执行的永远是最少延时的任务。 - 从CPU调度的过程中可以会产生的执行顺序的问题,或当一个线程执行到一半的时间被掉调度走的现象。
这个线程造成的原因就是没有保证原子性。 - 优化代码
- 再次观察一种极端情况
– 我们发现当我们把三个任务的延时时间设置为0的时候,结果只执行了任务1,我们进行调试
– 调试之后我们又发现是正常情况,但是运行时候不符合我们的预期结果,这时候我们不要慌,我们用jconsole工具去查看下扫描情况
- 我们发现在MyTimer。java22行出现了问题
1.创建一个定时器
2.向定时器添加任务1
3.第一个任务被添加到阻塞队列中
4.扫描线程启动,处理第一个任务
5.扫描线程1循环,获得第二个任务时候,队列为空,开始等待,同时扫描线程获得锁
6.主线程向阻塞队列添加任务时候,等待扫描对象的对象,由于扫描线程无法释放锁对象,主线程也就获取不到锁对象,造成相互等待,造成死锁
- 我们再次优化代码创造一个后台扫描线程,只做定时唤醒操作,定时1秒或者10ms,唤醒一次
-最终的代码
public class MyTimer {//用一个阻塞队列来组织任务private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();private Object lock = new Object();public MyTimer() {//创建线程Thread thread =new Thread(()->{while(true) {try {synchronized (this) {//从队列中取出任务MyTask task = this.queue.take();//判断有没有到执行的时间long currentTime = System.currentTimeMillis();if (currentTime >= task.getTime()) {//时间到了执行task.getRunnable().run();} else {//时间没到,将任务放回队列中long waitTime = task.getTime() - currentTime;this.queue.put(task);this.wait(waitTime);}}} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();//创建守护线程,定时唤醒一次Thread deamonThread=new Thread(()->{synchronized (this) {//唤醒一次this.notifyAll();//每隔100ms唤醒一次try {TimeUnit.MILLISECONDS.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//设置为守护线程deamonThread.setDaemon(true);deamonThread.start();}/*** 添加定时任务* @param runnable 任务* @param delay 延时* @throws InterruptedException*/public void schedule(Runnable runnable,long delay) throws InterruptedException {//根据传的参数构造一个MyTask对象MyTask task=new MyTask(runnable,delay);//将这个MyTask对象阻塞放入队列中queue.put(task);System.out.println("任务添加成功");}
}//MyTask类,用于封装任务和执行时间
class MyTask implements Comparable<MyTask>{//任务private Runnable runnable;//执行时间private long time;public MyTask(Runnable runnable, long delay) {//增强代码健壮性if(runnable==null){throw new IllegalArgumentException("任务不能为空");}if(delay<0) {throw new IllegalArgumentException("延迟时间不能为负数");}this.runnable = runnable;//计算出任务的执行时间this.time = delay+System.currentTimeMillis();}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {if(this.getTime()<o.getTime()){return -1;} else if(this.getTime()==o.getTime()){return 0;}else {return 1;}//万一时间超过了long的范围溢出,怎么办?用上面的比较比较好//return (int)(this.getTime()-o.getTime());}
public class Test {public static void main(String[] args) throws InterruptedException {MyTimer timer = new MyTimer();timer.schedule(new Runnable(){@Overridepublic void run() {System.out.println("任务1");}},0);timer.schedule(new Runnable(){@Overridepublic void run() {System.out.println("任务2");}},0);timer.schedule(new Runnable(){@Overridepublic void run() {System.out.println("任务3");}},0);//timer.schedule(null,-100);//任务加强健壮性}
}
工厂模式
- 先看出现的问题
我们这里造成了重载参数的相同,但是我们就是要这样的构造方法我们怎么解决呢?
工厂方法模式。根据不同的业务需求定义不同的方法来获取对象。
线程池
线程池的一些问题
- 什么是线程池
1.线程池就是一次创建多个线程,把这些线程放进一个池中,用的时候从池中取出,用完就还回去 - 为什么要用线程池
我们首先要明白,线程的创建和销毁都会消耗大量的资源,线程池中的线程当有任务的时候,就会执行任务,没有任务的时候就阻塞等待,并不销毁线程,线程池最⼤的好处就是减少每次启动、销毁线程的损耗。 - 为什么使用线程池可以提升效率
少量创建,少量销毁,创建一个线程要分为内核态和用户态,用户态相当于jvm层面,内核太相当于操作系统层面,当我们在jvm层面创建一个线程,就要在操作系统层面创建对应指令,就会消耗大量资源,消耗线程也如此,所以线程池减少了频繁的销毁和创建,用的时候就直接在线程池里面用已经创建多的,从而提升效率。 - 怎么用?
– jdk给我们提供了一组针对不同场景的线程池实例
public static void main(String[] args) {//1.用来处理大量短时间的任务的线程池,如果池没有可用的线程将创建线程,如果线程空闲60秒将收回并移除缓存ExecutorService cachedThreadpool= Executors.newCachedThreadPool();//2.创建一个操作无界队列,线程池大小固定的线程池ExecutorService fixedThreadpool= Executors.newFixedThreadPool(5);//可以指定线程数量//3.创建一个操作无界队列,只有一个线程的线程池ExecutorService singleThreadExecutor= Executors.newSingleThreadExecutor();//4.创建一个单线程执行器,可以加时间给定时间后执行或者定期执行ScheduledExecutorService singleThreadScheduledExecutor= Executors.newSingleThreadScheduledExecutor();//5.创建一个指定大小的线程池,可以加时间给定时间后执行或者定期执行ScheduledExecutorService scheduledThreadpool= Executors.newScheduledThreadPool(5);//6.创建一个指定大小(不传参,为当前机器的cpu核数)的线程池,并行处理任务,不保证处理顺序Executors.newWorkStealingPool();
}
Runtime.getRuntime().availableProcessors()
获取系统的cpu核数
实现一个线程池
- 先构思思路(先描述,再组织)
- 用Runable描述任务
- 组织管理任务可以使用一个队列,可以使用阻塞队列取实现
- 提供一个向队列的添加任务的方法
- 创建多个线程,扫描队列里面的任务,有任务时候执行,没有任务时候等待
public class MyExectorService {//定义阻塞队列阻止任务private BlockingQueue<Runnable> queue=new LinkedBlockingQueue(100);private static Object lock=new Object();public MyExectorService(int threadNum){for (int i = 0; i < threadNum; i++){Thread thread=new Thread(()->{//不停扫描队列while (true) {try {synchronized (lock){Runnable runable= queue.take();runable.run();}TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}//take()方法会阻塞,直到队列中有任务});//启动线程thread.start();}}/*** 提交任务到线程池中* @param runnable 具体的任务* @throws InterruptedException*/public void sumbit(Runnable runnable) throws InterruptedException {if(runnable==null){throw new IllegalArgumentException("任务不能为空");}//把任务加入队列中queue.put(runnable);}}
class Test1{public static void main(String[] args) throws InterruptedException {MyExectorService myExectorService=new MyExectorService(3);AtomicInteger j= new AtomicInteger();for (int i = 0; i < 10; i++) {myExectorService.sumbit(() -> {System.out.println(Thread.currentThread().getName() + " " + j);j.getAndIncrement();});if(i%3==0){TimeUnit.SECONDS.sleep(1);}}}
}
创建系统自带的线程池
- 前面jdk提供的线程池比较固定,也就是说我们不能自己定制,但是我们看底层代码时发现,这些线程池都是对ThreadPoolExecutor的封装
- 那我们可以根据ThreadPoolEecutor创建一个自定义线程池
用现实的两个例子去模拟线程工作的原理
周末去吃饭
银行办理业务
- 线程池的拒绝策略详解
- 我们注意一下,3和4是不会抛出异常的,1和2是会抛出异常的,放弃的任务永远都找不回来,所以指定拒绝策略的时候,要关注任务是不是必须要执行,如果必须要执行,就指定“返回调用者”,否则选1,3,4一个即可
public static void main(String[] args) {ThreadPoolExecutor threadPool=new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,new LinkedBlockingQueue<>(7),new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 100; i++) {int takeI=i;threadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+" 执行任务 "+takeI);}});}}
- 直接拒绝
实际只执行几个后面的都没执行
- 返回给调用者
有一部分代码返回给调用者main执行了
public static void main(String[] args) {ThreadPoolExecutor threadPool=new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,new LinkedBlockingQueue<>(7),new ThreadPoolExecutor.CallerRunsPolicy());for (int i = 0; i < 100; i++) {int takeI=i;threadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+" 执行任务 "+takeI);}});}}
- 放弃最早的任务
public static void main(String[] args) {ThreadPoolExecutor threadPool=new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,new LinkedBlockingQueue<>(7),new ThreadPoolExecutor.DiscardOldestPolicy());for (int i = 0; i < 100; i++) {int takeI=i;threadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+" 执行任务 "+takeI);}});}}
- 放弃最新的任务
public static void main(String[] args) {ThreadPoolExecutor threadPool=new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,new LinkedBlockingQueue<>(7),new ThreadPoolExecutor.DiscardPolicy());for (int i = 0; i < 100; i++) {int takeI=i;threadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+" 执行任务 "+takeI);}});}}
wait和sleep的区别
1.共同点,让线程休眠一会
2.从实现使用上来说是两种不同的方法
wait是Object类的方法,与锁相关,配合sychronized一起使用,调用wait之后会释放锁
sleep是Thread类的方法,与锁无关
wait可以通过notify和指定直线的方法唤醒,唤醒之后重新竞争锁资源
sleep只能通过超时时间唤醒
- 补充
相关文章:
JavaEE 【知识改变命运】05 多线程(4)
文章目录 单例模式什么是单例模式饿汉模式懒汉模式多线程- 懒汉模式分析多线程问题第一种添加sychronized的方式第二种添加sychronized的方式改进第二种添加sychronized的方式(DCL检查锁) 阻塞队列什么是阻塞队列什么是消费生产者模型标准库中的阻塞队列…...
迭代器和生成器
一、迭代器(Iterator) 1. 什么是迭代器? 迭代器是一个可以在某一集合(如列表、元组等)中逐个访问元素的对象。它提供了一个方法,可以记住遍历的位置,每次取出一个元素,直到所有元素…...
小发现,如何高级的顺序输出,逆序输出整数的每一位(栈,队列)
当我还是初学者的时候,我经常思考有没有比慢慢求每一位数字然后考虑正序,逆序输出要快的办法...长期琢磨,必有所获! 我刚学数据结构的时候还没意识到栈,队列还能这样用,虽然说有点杀鸡用牛刀的感觉&#x…...
前端换行、空格的多种表现形式
换行 1、<br> 标签 这是最直接的方式,用于在文本中插入一个简单的换行。<br> 标签是一个空元素,意味着它不需要结束标签。 示例: <p>这是第一行。<br>这是第二行。</p>2、CSS white-space 属性 通过CSS的w…...
自己总结:selenium高阶知识
全篇大概10000字(含代码),建议阅读时间30min 一、等待机制 如果有一些内容是通过Ajax加载的内容,那就需要等待内容加载完毕才能进行下一步操作。 为了避免人为操作等待,会遇到的问题, selenium将等待转换…...
无线遥控红外通信
无线遥控红外通信 红外发射装置一般是指红外遥控器由 键盘电路 ,红外编码电路 电源电路 和红外发射 电路组成 一般的红外线波长为940nm左右,外形与普通发光二极管相同 红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的方式传送…...
第一个C++程序--(蓝桥杯备考版)
第一个C程序 基础程序 #include <iostream>//头⽂件 using namespace std;//使⽤std的名字空间 int main()//main函数 {cout << "hello world!" << endl; //输出:在屏幕打印"hello world!" return 0;}main函数 main 函数是…...
Rust包管理和构建工具
Cargo 是 Rust 语言的包管理和构建工具。它提供了一套完整的工具链,用于管理 Rust 项目的依赖关系、编译代码、运行测试和生成文档。Cargo 极大地简化了 Rust 项目的开发和部署过程,使得开发者可以专注于编写代码,而不是处理构建系统的复杂性…...
STM32输入捕获详解
目录 一、引言 二、输入捕获原理 三、寄存器介绍 四、配置步骤 1.开启时钟 2.GPIO 初始化 3.初始化定时器 4.配置输入捕获模式 5.使能捕获和更新中断 6.设置中断分组并编写中断服务函数 7.使能定时器 五、程序示例 六、总结 一、引言 在嵌入式系统开发中࿰…...
利用高德地图API,如何在PHP与vue3中实现地图缩放功能
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
Selenium WebDriver:自动化网页交互的利器
Selenium WebDriver:自动化网页交互的利器 在当今快速发展的Web开发领域,自动化测试已经成为确保应用程序质量和用户体验的重要手段。Selenium WebDriver,作为Selenium工具包中的核心组件,正是这一领域的佼佼者。本文将详细介绍S…...
uniapp -- 实现页面滚动触底加载数据
效果 首选,是在pages.json配置开启下拉刷新 {"path": "pages/my/document/officialDocument","style": {"navigationStyle":</...
用ChatGPT-o1进行论文内容润色效果怎么样?
目录 1.引导问题发现 2.角色设定 3.整理常问修改 4.提供样例 5.小细节 小编在这篇文章中分享如何充分利用ChatGPT-o1-preview来提升论文润色的技巧。小编将持续跟进最新资源和最新的调研尝试结果,为宝子们补充更多实用的写作技巧。这些技巧将有助于您更有效地利…...
6 C/C++输⼊输出(下)(未完续)
1. OJ(online judge)题⽬输⼊情况汇总 在竞赛的 OJ 题⽬中,⼀般关于输⼊场景总结为下⾯四类: 接下来,我们就结合题⽬,给⼤家分别介绍。 1.1 单组测试⽤例 练习1 B2009 计算 (ab)/c 的值 - 洛谷 | 计算机科…...
题海拾贝:力扣 20、有效的括号
Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路! 我的博客:<但凡.-CSDN博客 我的专栏:《编程之路》、《题海拾贝》、《数据结构与算法之美》 欢迎点赞、关注! 1、题目 2、题解 这…...
视频推拉流EasyDSS无人机直播技术巡查焚烧、烟火情况
焚烧作为一种常见的废弃物处理方式,往往会对环境造成严重污染。因此,减少焚烧、推广绿色能源和循环经济成为重要措施。通过加强森林防灭火队伍能力建设与长效机制建立,各地努力减少因焚烧引发的森林火灾,保护生态环境。 巡察烟火…...
基于Hermite多项式的三维反时间波的生成
原创:daode3056(daode1212) 反时间波,也称为时间反演波,是一种在特定条件下能够实现波的聚焦和传播的技术。反时间波的产生基于时间反演技术,其原理和方法通常有: 1. [时间反演信号处理原理]: 时间反演技术并不是指时间…...
数据结构与算法复习AVL树插入过程
环境 $ cat /proc/version Linux version 6.8.0-45-generic (builddlcy02-amd64-115) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #45-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 30 12:02:04 UTC 2024 #include <std…...
MetaGPT源码 (Memory 类)
目录 MetaGPT源码:Memory 类例子 MetaGPT源码:Memory 类 这段代码定义了一个名为 Memory 的类,用于存储和管理消息(Message)对象。Memory 提供了多种操作消息的功能,包括添加单条或批量消息、按角色或内容筛选消息、删除最新消息…...
day1数据结构,关键字,内存空间存储与动态分区,释放
小练习 在堆区空间连续申请5个int类型大小空间,用来存放从终端输入的5个学生成绩,然后显示5个学生成绩,再将学生成绩升序排序,排序后,再次显示学生成绩。显示和排序分别用函数完成(两种排序方法࿰…...
C# 用封装dll 调用c++ dll 使用winapi
这里用c net 封装winapi函数 pch.h // pch.h: 这是预编译标头文件。 // 下方列出的文件仅编译一次,提高了将来生成的生成性能。 // 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 // 但是,如果此处列出的文件中的任何一个…...
vue2 如何设置i18n的默认语言为当前浏览器的语言
做到i18n这里设置默认语言的时候遇到了一些小问题,所以做个记录: 原始代码lang/index // index.js import Vue from vue import VueI18n from vue-i18n import Cookies from js-cookie // import elementEnLocale from element-ui/lib/locale/lang/en // element-…...
QT数据库SQLite:QsqlTableModel使用总结
数据库连接、数据模型与界面组件所涉及的类之间的关系如下所示: 数据库类 QSqlDatabase 类用于建立与数据库的连接,QSqlDatabase 对象就表示这种连接。QSqlDatabase 类的功能主要分为三大部分: 1、创建数据库连接,即创建 QSqlDat…...
服务器零配件
阵列卡 H3C电池 RAId 卡 内存条位置 HBA卡 MOC卡...
MySQL 学习 之 批量插入数据性能问题
文章目录 现象优化 现象 在使用 kettle 同步大数据的数据到我们的 MySQL 数据库中时发现,数据量大时插入效率很慢,大约在 2000/s 优化 在 MySQL 驱动连接中添加 rewriteBatchedStatementstrue 参数,减少 网络 IO DB IO 耗时 默认关闭指定…...
会话管理和身份验证和授权
Cookie、Session、Token Cookie 简介:[Cookie]是一种小型文本文件,由服务器发送到用户的浏览器并保存在用户的计算机上。其主要作用是识别用户身份、跟踪用户活动、保存用户设置等。Cookie通常由名称、值、域名、路径、过期时间等字段组成,并…...
RK3588 rknpu2/rkllm/rockit/mpp/rga 等源码验证
RK3588 简介 本项目基于rk3588硬件平台,将嵌入式、流媒体、AI等相关的技术验证源码地址 源码说明 buildroot 为buildroot使用方法dk_doc 为rk的文档mpp 在mpp例子上增加推流rga 为rk3588的硬件加速模块,可快速处理视频,提供的API接口与op…...
【CSS in Depth 2 精译_075】12.2 Web 字体简介 + 12.3 谷歌字体的用法
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第四部分 视觉增强技术 ✔️【第 12 章 CSS 排版与间距】 ✔️ 12.1 间距设置 12.1.1 使用 em 还是 px12.1.2 对行高的深入思考12.1.3 行内元素的间距设置 12.2 Web 字体 ✔️12.3 谷歌字体 ✔️12.…...
【数字花园】个人知识库网站搭建:①netlify免费搭建数字花园
目录 [[数字花园]]的构建原理包括三个步骤:五个部署方案教程相关教程使用的平台 步骤信息管理 这里记录的自己搭建数字花园(在线个人知识库)的经历,首先尝试的是网上普遍使用的方法,也就是本篇文章介绍的。 后面会继续…...
访问者模式的理解和实践
在软件开发过程中,设计模式为我们提供了解决常见问题的最佳实践。访问者模式(Visitor Pattern)是行为设计模式之一,它将数据操作与数据结构分离,使得在不修改数据结构的前提下,能够定义作用于这些元素的新的…...
SpringBoot中Selenium详解
文章目录 SpringBoot中Selenium详解一、引言二、集成Selenium1、环境准备1.1、添加依赖 2、编写测试代码2.1、测试主类2.2、页面对象2.3、搜索组件 三、使用示例四、总结 SpringBoot中Selenium详解 一、引言 在现代软件开发中,自动化测试是提高软件质量、减少重复…...
Android 系统应用重名install安装失败分析解决
Android 系统应用重名install安装失败分析解决 文章目录 Android 系统应用重名install安装失败分析解决一、前言1、Android Persistent apps 简单介绍 二、系统 persistent 应用直接安装需求分析解决1、系统应用安装报错返回的信息2、分析解决 三、其他1、persistent系统应用in…...
scala中如何解决乘机排名相关的问题
任务目标: 1.计算每个同学的总分和平均分 2.按总分排名,取前三名 3.按单科排名,取前三名 好的,我们可以用Scala来完成这个任务。下面是一个简单的示例代码,它将演示如何实现这些功能: // 假设我们有一个…...
常用的注解
RequestMapping 用于映射请求路径 可以添加在类或方法上 请求类型 请求类型包括GET、POST、PUT、DELETE等 默认支持GET和POST两种方式 简写:GetMapping、PostMapping、PutMapping、DeleteMapping PostMapping("/buy") 等价 RequestMapping("/buy&quo…...
移动应用渗透测试:确保通过测试的关键安全策略
无论您是为了维持合规性、保护敏感用户数据,还是维护品牌声誉,顺利通过渗透测试(Pen Test)都是至关重要的。为了帮助您轻松应对这一过程,有几个积极的安全措施可以帮助确保您的应用程序更加安全。 通过采用高级安全机…...
【Canvas与光阑】立方体六彩光阑
【成图】 120*120的png图标 大小图: 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>立方体 六彩光阑 Draft2</…...
【ArcGIS微课1000例】0135:自动生成标识码(长度不变,前面自动加0)
文章目录 一、加载实验数据二、BSM计算方法一、加载实验数据 加载专栏《ArcGIS微课实验1000例(附数据)》配套数据中0135.rar中的建筑物数据,如下图所示: 打开属性表,BSM为数据库中要求的字段:以TD_T 1066-2021《不动产登记数据库标准》为例: 计算出来的BSM如下图: 二、B…...
nginx文件上传下载控制
上传大小控制 client_max_body_size 设置最大客户端请求体大小 默认大小1M,可以使用在http, server, location块。 根据不同的请求路径设置不同的大小控制 server {listen 9001;client_max_body_size 2M;location / {root D:\\server\\nginx-1.22.0\\html\\9001;}locat…...
LabelImg使用教程
(yolov5scondaPython3123) D:\PyCharm20240724\20240724PyCharmProject>conda.bat deactivate D:\PyCharm20240724\20240724PyCharmProject>conda activate labelimg_env (labelimg_env) D:\PyCharm20240724\20240724PyCharmProject> labelimg 创建快捷键方式...
运维新手入门——KVM(Beginner‘s Guide to Operations and Maintenance - kvm)
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…...
Android 10.0 WiFi连接默认设置静态IP地址功能实现
1.前言 在10.0的系统rom定制化开发中,在定制化某些功能开发中,在wifi模块中,有产品需要要求设置wifi静态ip功能,而系统中wifi连接 后ip是动态的,每次开机后 连接wifi的ip就是不固定的,所以产品需要采用固定ip,就需要实现静态ip功能 2.WiFi连接默认设置静态IP地址功能实…...
ceph /etc/ceph-csi-config/config.json: no such file or directory
环境 rook-ceph 部署的 ceph。 问题 kubectl describe pod dragonfly-redis-master-0Warning FailedMount 7m59s (x20 over 46m) kubelet MountVolume.MountDevice failed for volume "pvc-c63e159a-c940-4001-bf0d-e6141634cc55" : rpc error: cod…...
windows C#-限制可访问性
属性或索引器的 get 和 set 部分称为访问器。 默认情况下,这些访问器具有与其所属属性或索引器相同的可见性或访问级别。不过,有时限制对其中某个访问器的访问是有益的。 通常,限制 set 访问器的可访问性,同时保持 get 访问器可公…...
Java-22 深入浅出 MyBatis - 手写ORM框架3 手写SqlSession、Executor 工作原理
点一下关注吧!!!非常感谢!!持续更新!!! 大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了: MyBatisÿ…...
【数据分享】1901-2023年我国省市县三级逐年最低气温数据(Shp/Excel格式)
之前我们分享过1901-2023年1km分辨率逐月最低气温栅格数据和Excel和Shp格式的省市县三级逐月最低气温数据,原始的逐月最低气温栅格数据来源于彭守璋学者在国家青藏高原科学数据中心平台上分享的数据!基于逐月栅格数据我们采用求年平均值的方法得到逐年最…...
AlphaPose、yolov8Pose、RTMPose进行对比
一、Alphapose 参考: https://blog.csdn.net/m0_45850873/article/details/123939849...
【Linux】文件系统
文章目录 Group中的组成部分inode tableinode bitmapdata blocksblock bitmapgroup descriptor tablesuper block 文件系统关于inode和blocksinode和block是如何映射的?12个直接映射一级间接索引二级间接索引三级间接索引 为什么访问文件的是inode,但是我…...
希迪智驾持续亏损8.2亿:毛利率下滑,冲刺“自动驾驶矿卡第一股”
《港湾商业观察》黄懿 近日,希迪智驾(湖南)股份有限公司(下称“希迪智驾”)向港交所主板递交上市申请,联席保荐人为中金公司、中信建投国际、中国平安资本(香港)。 资料显示&#…...
Python实现中国象棋
探索中国象棋 Python 代码实现:从规则逻辑到游戏呈现 中国象棋,这款源远流长的棋类游戏,承载着深厚的文化底蕴与策略智慧。如今,借助 Python 与 Pygame 库,我们能够在数字世界中复刻其魅力,深入探究代码背后…...
C++小碗菜之五:GDB调试工具
“程序员不是编写代码的人,而是调试错误的人。” – 约翰本尼斯(John Bennet) 目录 前言 在虚拟机中安装 GDB GDB调试的实战演练 创建示例代码 例子: 使用 GDB 调试 编译代码 启动 GDB 设置断点 运行程序 打印变量值 …...