JAVA JUC学习笔记

news2024/11/26 21:47:26

基础知识

1、进程和线程的对比

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  • 进程间通信较为复杂
    • 同一台计算机的进程通信称为 IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

2、并行与并发

  • 并发(concurrent)是同一时间应对多件事情的能力
  • 并行(parallel)是同一时间动手做多件事情的能力

3、异步与同步

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异

Java线程

1、创建线程

方法一:使用 T h r e a d \color{red}{Thread} Thread

// 创建线程对象
Thread t = new Thread() {
 public void run() {
 // 要执行的任务
 }
};
// 启动线程
t.start();

方法一:使用 R u n n a b l e \color{red}{Runnable} Runnable配合Thread

把【线程】和【任务】(要执行的代码)分开

  • Thread 代表线程
  • Runnable 可运行的任务(线程要执行的代码)
Runnable runnable = new Runnable() {
 public void run(){
 // 要执行的任务
 }
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start(); 

原理之 Thread 与 Runnable 的关系

  • 方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
  • 用 Runnable 更容易与线程池等高级 API 配合
  • 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

方法三 F u t u r e T a s k \color{red}{FutureTask} FutureTask配合 Threa

FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况

// 创建任务对象
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return null;
            }
        });
        
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t = new Thread(task);
t.start();

// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);

2、线程运行原理

每个线程启动后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

在这里插入图片描述

3、线程上下文切换

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

4、常见方法

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

5、run和start

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

6、sleep与yield

  • Sleep
    1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
    2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛InterruptedException
    3. 睡眠结束后的线程未必会立刻得到执行
    4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
  • yield
    1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
    2. 具体的实现依赖于操作系统的任务调度器(CPU闲,让也让不出去)

区别:

  • Runnable还是有机会被调用的,但任务调度器不会把时间片分配给Timed Waiting

7、程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
Runnable task1 = () -> {
 	int count = 0;
	 for (;;) {
 		System.out.println("---->1 " + count++);
    }
 };
 Runnable task2 = () -> {
 	int count = 0;
 	for (;;) {
 		// Thread.yield();
 		System.out.println("              ---->2 " + count++);
    }
 };
 Thread t1 = new Thread(task1, "t1");
 Thread t2 = new Thread(task2, "t2");
 // t1.setPriority(Thread.MIN_PRIORITY);
 // t2.setPriority(Thread.MAX_PRIORITY);
 t1.start();
 t2.start();

8、join方法

等待某个进程的运行结果

static int r = 0;
 public static void main(String[] args) throws InterruptedException {
 	test1();
 }
 private static void test1() throws InterruptedException {
 	log.debug("开始");
 	Thread t1 = new Thread(() -> {
 		log.debug("开始");
 		sleep(1);
 		log.debug("结束");
 		r = 10;
    });
 	t1.start();
 	log.debug("结果为:{}", r);
 	log.debug("结束");
 }

输出:0

分析

  • 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出
  • 而主线程一开始就要打印 r 的结果,所以只能打印出r=0

就想得到10,怎么办?

可以使用join,等待t1的返回结果

static int r = 0;
 public static void main(String[] args) throws InterruptedException {
 	test1();
 }
 private static void test1() throws InterruptedException {
	 log.debug("开始");
	 Thread t1 = new Thread(() -> {
	 	log.debug("开始");
	 	sleep(1);
	 	log.debug("结束");
	 	r = 10;
	 });
	 t1.start();
	 t1.join();
	 log.debug("结果为:{}", r);
	 log.debug("结束");
 }

9、interrupt方法

打断 sleep,wait,join 的线程 ,这几个方法都会让线程进入阻塞状态

打断 sleep 的线程, 会清空打断状态(变成false),以 sleep 为例

private static void test1() throws InterruptedException {
	 Thread t1 = new Thread(()->{
	 	sleep(1);
	 }, "t1");
	 t1.start();
}

//main方法中
sleep(0.5);
t1.interrupt();
log.debug(" 打断状态: {}", t1.isInterrupted())

打断标记:
该线程是不是被其他的线程所干扰,打断过。

打断正常线程时,其实并没有打断,只是改变打断标记(不会清空打断状态),线程还会继续运行,需要我们手动利用打断标记的布尔值去判断进行打断

private static void test2() throws InterruptedException {
	 Thread t2 = new Thread(()->{
		 while(true) {
			 Thread current = Thread.currentThread();
			 boolean interrupted = current.isInterrupted();
			 if(interrupted) {
			 log.debug(" 打断状态: {}", interrupted);
			 break;
			            }
			        }
		    }, 
	"t2");
	 t2.start();
	 sleep(0.5);
	 t2.interrupt();
 }

10、两阶段终止模式

在一个线程 T1 中如何“优雅”终止线程 T2?这里的【优雅】指的是给 T2 一个料理后事的机会。

在这里插入图片描述

class TPTInterrupt {
 	private Thread thread;
 	public void start(){
	 	thread = new Thread(() -> {
		 	while(true) {
		 		Thread current = Thread.currentThread();
		 		if(current.isInterrupted()) {
		 			log.debug("料理后事");
		 			break;
		       }
			 	try {
			 		Thread.sleep(1000);
			 		log.debug("将结果保存");
			   	}catch (InterruptedException e) {
			 		current.interrupt();
			  	}
			 	// 执行监控操作               
			 }
	   	},"监控线程");
	 	thread.start();
    }
 	public void stop() {
 		thread.interrupt();
    }
 }

调用

TPTInterrupt t = new TPTInterrupt();
 t.start();
 Thread.sleep(3500);
 log.debug("stop");
 t.stop()

11、不推荐使用的方法

这些方法已过时,容易破坏同步代码块,造成线程死锁

在这里插入图片描述

12、主线程与手护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

log.debug("开始运行...");
Thread t1 = new Thread(() -> {
		log.debug("开始运行...");
		sleep(2);
		log.debug("运行结束...");
 	}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
sleep(1);
log.debug("运行结束...");

在这里插入图片描述

线程的状态

1、从操作系统层面看

在这里插入图片描述

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 【运行状态】指获取了 CPU 时间片运行中的状态
    • 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
  • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
  • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状

2、从Java API层面来描述

根据 Thread.State 枚举,分为六种状态
在这里插入图片描述

  • NEW 线程刚被创建,但是还没有调用start() 方法
  • RUNNABLE 当调用了start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  • BLOCKED ,WAITING ,TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
  • TERMINATED 当线程代码运行结束

共享模型之管程

1、共享问题

在这里插入图片描述
最后可能会出现整数或者负数,原因在于上下文的切换:

  • 出现负数:
    在这里插入图片描述

  • 出现整数:
    在这里插入图片描述

2、临界区

  • 一个程序运行多个线程本身是没有问题的
  • 问题出在多个线程访问共享资源
    • 多个线程读共享资源其实也没有问题
    • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界

竞态条件 Race Condition

  • 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

3、synchronized解决方案

使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

在这里插入图片描述

4、方法上的synchronized

在这里插入图片描述

不加 synchronized 的方法

  • 不加 synchronzied 的方法就好比不遵守规则的人,不去老实排队(好比翻窗户进去的)

5、变量的线程安全分析

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

  • 如果它们没有共享,则线程安全
  • 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
    • 如果只有读操作,则线程安全
    • 如果有读写操作,则这段代码是临界区,需要考虑线程安全
      局部变量是否线程安全?
  • 局部变量是线程安全的
  • 但局部变量引用的对象则未必
    • 如果该对象没有逃离方法的作用访问,它是线程安全的
    • 如果该对象逃离方法的作用范围,需要考虑线程安全

6、常见线程安全类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类

它们的每个方法是原子的
但注意它们多个方法的组合不是原子的

如:
在这里插入图片描述
不可变类线程安全性

  • String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的

7、Monitor概念

Monitor,在操作系统领域一般翻译为“管程”,在Java邻域就是“对象锁”。

Java对象头,以32位虚拟机为例

在这里插入图片描述
其中Klass word指定该对象的类型,即指向一个class对象。

Mark Word的不同状态对应不同的结构,如下:
在这里插入图片描述

其中Normal表示该对象是正常状态,hashcode表示每个对象不同的哈希码,age表示分代的年龄,垃圾回收算法可以根据此将其放入老年区。

每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象加锁,对应上图的Heavyweight Locked,该对象头的Mark Word中的ptr_to_heavyweight_monitor就会指向Monitor。

以一段代码为例:

synchronized(obj){
//临界资源
}
  • 当线程Thread2执行synchronized(obj),就会将MarkWord中的指针指向Monitor
  • 刚开始,Monitor中的Owner为null,Thread会将Monitor的所有者Owner设置为Thread2,Owner只能指向一个线程
  • 当别的线程Thread1执行synchronized(obj),会检查obj有没有关联Monitor,然后检查关联的Monitor有没有主人,即Owner
  • 此时,Thread1就会进入EntryList,可以理解成堵塞队列,然后线程Thread1变成BLOCKED状态
    在这里插入图片描述
  • 当Thread2执行完毕,就会让出Owner的位置。然后从Monitor的EntryList中唤醒一个线程,成为该Monitor的下一个主人。

注意:

  • synchronized 必须是进入同一个对象的 monitor 才有上述的效果
  • 不加 synchronized 的对象不会关联 monitor ,不遵从以上规则

8、synchronized原理进阶

轻量级锁

使用场景:
如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是synchronized

举例子:

static final Object obj = new Object();
public static void method1() {
	synchronized( obj ) {
 		// 同步块 A
		method2();
	}
}
public static void method2() {
	synchronized( obj ) {
 	// 同步块 B
	}
}
  • 当method1执行到synchronized,会创建锁记录(Lock Record)对象,每个栈帧都会包含一个锁记录的结构:
    在这里插入图片描述

    • Object reference指向锁对象,并尝试用cas替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录
      在这里插入图片描述
  • 如果cas替换成果,对象头中的Mark Word存储了锁记录地址和状态00,表示该线程给对象加锁。此时图示如下:
    在这里插入图片描述

  • cas失败的两种情况:

    • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
    • 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数。(如代码执行到上述method2的synchronized部分)
      在这里插入图片描述
      即要进行cas交换时,发现Mark Word中的状态是00,且锁记录指向的是本线程。此时会添加一条锁记录,且将锁记录中存储Mark Word的部分设置为null
  • 当退出synchronized如果有取值为null的锁记录,表示有重入。此时,将重置锁记录,表示重入计数减一
    在这里插入图片描述

  • 当退出synchronized代码块(解锁时)锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头

    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

  • 当Thread1进行轻量级加锁,Thread0已经加了轻量级锁
    在这里插入图片描述

  • 此时,Thread1加锁失败,进入锁膨胀流程

    • 为Object对象申请Monitor锁,让Object指向重量级锁地址
    • 然后将自己加入到Monitor的EntryList,进入BLOCKED状态
      在这里插入图片描述
  • 当Thread0退出同步代码块解锁时,使用cas将Mark Word的值恢复给对象头,失败(因为此时存储的是Monitor的地址)。然后进入重量级解锁流程,按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中的BLOCKED线程

自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞(阻塞会造成上下文切换)。

偏向锁

轻量级锁在没有竞争时,每次重入任需要执行CAS判断。

因此引入偏向锁做进一步优化:

  • 只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID就是自己就表示没有竞争,不用重新CAS。
  • 以后只要不发生竞争,这个对象就归该线程所有

回忆对象头格式:
在这里插入图片描述
一个对象创建时:

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

注意

  • 处于偏向锁的对象解锁后,线程 id 仍存储于对象头中

撤销偏向锁

  • 调用锁对象的hashCode:
    偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销(因为没有位置存储hashCode了)
    • 轻量级锁会在锁记录中记录 hashCode
    • 重量级锁会在 Monitor 中记录 hashCode
  • 其它线程使用对象:
    当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

批量重偏向

若对象被多个线程访问,但没有竞争,这时偏向了现场T1的对象仍有机会重新偏向T2,重偏向会重置对象的Thread ID

例子:

输出
private static void test3() throws InterruptedException {
 
	Vector<Dog> list = new Vector<>();
 	Thread t1 = new Thread(() -> {
 		for (int i = 0; i < 30; i++) {
 			Dog d = new Dog();
 			list.add(d);
 			synchronized (d) {
 				log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
 			}
 		}
 		synchronized (list) {
 			list.notify();
 		} 
 	}, "t1");
 	t1.start();
 
 
 	Thread t2 = new Thread(() -> {
 		synchronized (list) {
 			try {
 				list.wait();
 			} catch (InterruptedException e) {
 				e.printStackTrace();
 			}
 		}
 		log.debug("===============> ");
 		for (int i = 0; i < 30; i++) {
 			Dog d = list.get(i);
 			log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
 			synchronized (d) {
 				log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
 			}
 			log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
	 	}
 	}, "t2");
 	t2.start();
}
  • 第一次cas会启用偏向锁,偏向t1
  • 因为没有竞争,因此不会发生撤销偏向锁
  • 当进入t2时,标识为撤销锁偏向001,再次申请锁会将偏向锁升级为轻量级锁
  • 在后面几次循环中会保持轻量级锁的申请模式
  • 当撤销锁偏向达到某个阈值,会将轻量级锁转变成偏向锁偏向t2进程

批量撤销

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

9、wait、notify

  • obj.wait() 让进入 object 监视器的线程到 waitSet 等待

    • wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify 为止
    • wait(long n) 有时限的等待, 到 n 毫秒后结束等待继续执行后续代码,或是被 notify
  • obj.notify() 在 object 上正在 waitSet 等待的线程中随机挑一个唤醒

  • obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

wait和sleep的区别

  • sleep是Thread类的方法,wait是Object的方法
  • sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用,在获取对象锁之后,才能使用wait
  • sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
  • 它们状态都是TIMED_WAITING

wait、notify的使用格式

synchronized(lock) {
	while(条件不成立) {
		lock.wait();
	}
 // 干活
}
 
//另一个线程
synchronized(lock) {
 lock.notifyAll();
}

wait、notify原理

在这里插入图片描述

  • Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
  • BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片
  • BLOCKED线程会在Owner线程释放锁时唤醒
  • WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味立即获得锁,仍需进入EntryList重新竞争

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

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

相关文章

前端面试 vue 按钮级的权限控制

方案一 按钮权限也可以用v-if判断 但是如果页面过多&#xff0c;每个页面页面都要获取用户权限role和路由表里的meta.btnPermissions&#xff0c;然后再做判断 这种方式就不展开举例了 方案二 使用自定义指令实现 按钮级的权限控制 思维导图 心就是自定义指令的书写 首先…

exo 大模型算力共享;Llama3-70B是什么

目录 exo 大模型算力共享 exo框架的特点 如何使用exo框架 注意事项 结论 Llama3-70B是什么 一、基本信息 二、技术特点 三、性能与应用 四、未来发展 exo 大模型算力共享 exo框架的特点 异构支持:支持多种不同类型的设备,包括智能手机、平板电脑、笔记本电脑以及高…

Java 集合框架:Java 中的双端队列 ArrayDeque 的实现

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 019 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

AI多模态实战教程:面壁智能MiniCPM-V多模态大模型问答交互、llama.cpp模型量化和推理

一、项目简介 MiniCPM-V 系列是专为视觉-语⾔理解设计的多模态⼤型语⾔模型&#xff08;MLLMs&#xff09;&#xff0c;提供⾼质量的⽂本输出&#xff0c;已发布4个版本。 1.1 主要模型及特性 &#xff08;1&#xff09;MiniCPM-Llama3-V 2.5&#xff1a; 参数规模: 8B性能…

初学Linux之常见指令(下)

初学Linux之常见指令&#xff08;下&#xff09; 文章目录 初学Linux之常见指令&#xff08;下&#xff09;1. echo 指令2. cat 指令3. more 指令4. less 指令5. head 和 tail 指令6. date 指令7. cal 指令8. which 指令9. alias 指令10. find 指令11. grep 指令12. zip 和 unz…

C++中如何高效拼接两个vector

在C编程中&#xff0c;vector是一种常用的数据结构&#xff0c;它代表了一个可以动态改变大小的数组。在实际开发中&#xff0c;经常需要将两个vector拼接在一起&#xff0c;形成一个新的vector。本文将详细介绍如何在C中拼接两个vector&#xff0c;并探讨不同方法的性能差异。…

初学51单片机之指针基础与串口通信应用

开始之前推荐一个电路学习软件&#xff0c;这个软件笔者也刚接触。名字是Circuit有在线版本和不在线版本&#xff0c;这是笔者在B站看视频翻到的。 Paul Falstadhttps://www.falstad.com/这是地址。 离线版本在网站内点这个进去 根据你的系统下载你需要的版本红线的是windows…

PMP--知识卡片--敏捷方法

文章目录 敏捷方法&#xff0c;是一种新型软件开发方法。不要求遵循传统的软件开发流程&#xff0c;强调快速开发和有效适应需求变化&#xff0c;典型代表如看板、Scrumban、极限编程、测试驱动开发等。 区别于传统项目场景&#xff0c;敏捷项目场景强调交互协作、尊重个体、面…

Linux系统快速搭建轻量化网站Halo并实现无公网IP远程访问

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

无价之美:大麗和和品牌美学概念宣传片发布

高级珠宝品牌大麗和和筹备6个月&#xff0c;隆重发布《无价之美》品牌美学概念宣传片。品牌创始人牟童女士携珍贵藏品&#xff0c;亲诉美与传承的故事。 “尊尚正美” 珍稀宝石的颜色&#xff0c;是以色正为美。“正”就是事物极致的样子。最受人追慕的翡翠被称为“帝王绿”&a…

drawio更改默认字体大小(暂时有问题,修改中)

PS&#xff1a;其他的也可以在这里修改对应的值

Docker无法拉取镜像!如何解决?

问题现象 继去年Docker Hub被xxx后&#xff0c;各大NAS的注册表均出现问题&#xff0c;例如群晖的Docker套件注册表无法连接&#xff08;更新至DSM7.2版本后恢复&#xff09;。而在今年2024年6月初&#xff08;约2024.06.06&#xff09;&#xff0c;NAS中最重要的工具Docker又…

解决git拉取代码报错:Couldn‘t connect to server

前言&#xff1a; 今天在拉取git仓库代码的时候&#xff0c;报错&#xff1a;fatal: unable to access https://codeup.aliyun.com/fly/business-project/lezhi-HR.git/: Failed to connect to 127.0.0.1 port 8020 after 2082 ms: Couldnt connect to server 错误截图&#…

mysql中的索引和分区

目录 1.编写目的 2.索引 2.1 创建方法 2.2 最佳适用 2.3 索引相关语句 3.分区 3.1 创建方法 3.2 最佳适用 Welcome to Code Blocks blog 本篇文章主要介绍了 [Mysql中的分区和索引] ❤博主广交技术好友&#xff0c;喜欢文章的可以关注一下❤ 1.编写目的 在MySQL中&…

函数调用过程

生成机器码.o文件&#xff0c;使用objdump - d -M intel hello_func.o来看汇编代码 栈内存由于历史原因看作是从高地址往低地址扩张所以栈底为高地址&#xff0c;栈顶为低地址。 rbp存储的时当前栈帧的基地址&#xff0c;栈底地址。 rsp存储的是栈顶地址&#xff0c;rip存储…

【计算机网络】TCP/IP——流量控制与拥塞控制

学习日期&#xff1a;2024.7.22 内容摘要&#xff1a;TCP的流量控制与拥塞控制 流量控制 一般来说&#xff0c;我们总是希望数据传输的快一些&#xff0c;但是如果数据传输的太快&#xff0c;接收方可能就来不及接收&#xff0c;这就会导致数据的丢失&#xff0c;流量控制正是…

【代码随想录】【算法训练营】【第58天 4】 [卡码104]建造最大岛屿

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 卡码网。 day 58&#xff0c;周四&#xff0c;ding~ 题目详情 [卡码104] 建造最大岛屿 题目描述 卡码104 建造最大岛屿 LeetCode类似题目827 最大人工岛 解题思路 前提&#xff1a; 思路&#xff1a; 重点…

压缩pdf大小的方法 指定大小软件且清晰

在数字化时代&#xff0c;pdf文件因其良好的兼容性和稳定性&#xff0c;已成为文档分享的主流格式。然而&#xff0c;高版本的pdf文件往往体积较大&#xff0c;传输和存储都相对困难。本文将为您详细介绍几种简单有效的方法&#xff0c;帮助您减小pdf文件的大小&#xff0c;让您…

基于STM32单片机生理监控心率脉搏TFT彩屏波形曲线

基于STM32单片机生理监控心率脉搏TFT彩屏波形曲线 1、系统功能介绍2、演示视频3、系统框图4、系统电路介绍4.1、STM32单片机最小系统设计4.2、按键电路设计4.3、蜂鸣器报警电路设计4.4、Pulsesensor脉搏心率传感器模块电路设计 5、程序设计5.1、LCD TFT屏幕初始化5.2、TFT屏幕显…

【性能优化】在大批量数据下使用 HTML+CSS实现走马灯,防止页面卡顿(一)

切换效果 页面结构变化 1.需求背景 项目首页存有一个小的轮播模块,保密原因大概只能这么展示,左侧图片右侧文字,后端一次性返回几百条数据(开发环境下,生产环境只会更多).无法使用分页解决,前端需要懒加载防止页面卡顿 写个小demo演示,如下 2.解决思路 获取到数据后,取第一…