Java EE初阶——线程安全

1. 线程的状态

1. 线程状态分类(Thread.State 枚举)

Java 定义了 6 种线程状态,这些状态均由 java.lang.Thread.State 枚举表示:

  1. NEW(新建)
    线程对象已创建,但尚未调用 start() 方法。此时线程尚未启动,只是一个普通的 Java 对象。

  2. RUNNABLE(可运行)
    线程已调用 start() 方法,正在 JVM 中运行。该状态包含两种实际情况:

    • READY(就绪):线程已获取除 CPU 外的所有资源,等待操作系统调度。
    • RUNNING(运行中):线程正在 CPU 上执行。
  3. BLOCKED(阻塞)
    线程因等待监视器锁(如进入 synchronized 块 / 方法)而被阻塞。线程会在获取锁后恢复为 RUNNABLE 状态。

  4. WAITING(无限期等待)
    线程因调用以下方法而进入无限期等待状态,必须等待其他线程显式唤醒:

    • Object.wait()
    • Thread.join()
    • LockSupport.park()
    • 唤醒条件

      • notify()/notifyAll()

      • 目标线程终止(针对 join()

  5. TIMED_WAITING(限期等待)
    线程因调用以下带超时参数的方法而进入限期等待状态,超时后自动唤醒:

    • Thread.sleep(long millis)
    • Object.wait(long timeout)
    • Thread.join(long millis)
    • LockSupport.parkNanos()
    • LockSupport.parkUntil()
  6. TERMINATED(终止)
    线程执行完毕(run() 方法正常退出)或因异常终止,线程生命周期结束。

2. 线程的状态和转移

1.  NEW RUNNABLE TERMINATED 状态的转换
public class ThreadDomo1 {public static void main(String[] args) {Thread t = new Thread(()->{for(int i=0;i<1;i++){}});System.out.println(t.getState());//NEWt.start();while(t.isAlive()){//线程存活System.out.println(t.getState());//RUNNABLE}System.out.println(t.getState());//TERMINATED}
}
  1. 线程状态不可逆:线程一旦进入 TERMINATED 状态,无法再次启动(调用 start() 会抛出 IllegalThreadStateException)。
  2. BLOCKED 与 WAITING 的区别
    • BLOCKED 是因等待监视器锁而阻塞。
    • WAITING/TIMED_WAITING 是主动调用方法进入等待状态,需显式唤醒或超时。

2. 线程安全

Java 标准库中的线程安全类

1. Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, ⼜没有任何加锁措施.
ArrayList
LinkedList
HashMap
TreeMap
HashSet
TreeSet
StringBuilder
2. 但是还有⼀些是线程安全的. 使⽤了⼀些锁机制来控制.
Vector (不推荐使⽤)
HashTable (不推荐使⽤)
ConcurrentHashMap
StringBuffer

3. 还有的虽然没有加锁, 但是不涉及 "修改", 仍然是线程安全的
String

线程安全是多线程编程中的核心概念,指的是在多线程环境下,程序的行为和结果与单线程环境下一致,不会出现数据竞争、不一致或其他意外情况。

如果这个代码在单线程环境下运行正确,在多线程环境下产生 bug ,这种情况就称为“线程不安全”或“存在线程安全问题” 。

public class ThreadDomo2 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for(int i=0;i<50000;i++){count++;}});Thread t2 = new Thread(()->{for(int i=0;i<50000;i++){count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);//预期结果 10w}
}

运行代码发现,结果每次都不一样,结果大概在5w-10w之间,这是为什么?

1. 线程不安全原因

1. 【根本原因】线程调度是随机的

操作系统上的线程是“抢占式执行” “随机调度” 

2. 修改共享数据

代码结构:进程中多个线程同时修改同一个变量

  没有问题:1. 一个线程修改一个变量

                    2. 多个线程读取同一个变量

                    3. 多个线程修改不同变量

3. 【直接原因】原子性

多线程同时修改同一个变量操作不是原子操作

count++; 由三个指令构成:

  1. load  从内存中读取数据到 cpu 寄存器

  2. add 把寄存器数值 +1

  3. save 把寄存器的值写回到 内存 中

t1 和 t2 是并发执行的,可能交错执行这三步,导致部分增量丢失。

1,2 为线程安全,其余都为线程不安全

关键在于,要确保前一个线程 save 之后,第二个线程再 load ,否则第二个线程 load 到的结果就是第一个线程自增前的结果,两次自增就只 +1

即一个线程执行 1-n(基本为1次)这自增,被另一个线程覆盖成自增 1 次的情况。

4. 可见性

可⻅性指⼀个线程对共享变量值的修改,能够及时地被其他线程看到.
Java 内存模型 (JMM): Java虚拟机规范中定义了Java内存模型.
⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果.

线程之间的共享变量存在 主内存 (Main Memory).
每⼀个线程都有⾃⼰的 "⼯作内存" (Working Memory) .
当线程要读取⼀个共享变量的时候, 会先把变量从主内存拷⻉到⼯作内存, 再从⼯作内存读取数据.
当线程要修改⼀个共享变量的时候, 也会先修改⼯作内存中的副本, 再同步回主内存.
所谓的 "主内存" 才是真正硬件⻆度的 "内存". ⽽所谓的 "⼯作内存", 则是指 CPU 的寄存器和⾼速缓存.
CPU 访问⾃⾝寄存器的速度以及⾼速缓存的速度, 远远超过访问内存的速度(快了 3 - 4 个数量级, 也就是⼏千倍, 上万倍).
import java.util.Scanner;public class ThreadDomo3 {public static int flg = 1;//使用 volatile 关键字//public static volatile int flg = 1;public static void main(String[] args) throws InterruptedException {Scanner scan = new Scanner(System.in);Thread t1 = new Thread(()->{while(flg==1){ // 循环检查 flg 的值// 空循环,等待 flg 变为非 1}System.out.println("t1 线程结束");});Thread t2 = new Thread(()->{System.out.print("请输入flg的值:");flg = scan.nextInt();// 从控制台读取输入,修改 flg 的值System.out.println("t2 线程结束");});t1.start();t2.start();}
}

   

使用 volatile 关键字

在该代码中,我们预期是通过 t2 线程输入一个非 1 数,使 t1 线程结束,事实却是我们输入非 1 整数,t1 线程并未结束,这是为什么?

while(flg == 1);核心指令有两条:

1. load 读取内存中 flg 的值到cpu寄存器中

2. 拿寄存器中获取的值与1进行比较(条件跳转指令)

频繁执行 load 操作和条件跳转,load 操作执行的结果,每次都是一样的,且 load 操作开销远远高于条件跳转,访问寄存器的操作速度远远超过访问内存,此时 jvm 就可能做出代码优化,优化掉 load 操作,以提高循环的执行速度。却导致 t2 线程对共享变量的修改无法及时被 t1 线程看到。造成线程不安全。

这种优化被称为 循环不变代码外提(Loop Invariant Code Motion),它将循环内不变的操作(如 load)移到循环外,大幅提高执行效率。但在多线程环境下,这种优化会导致 内存可见性问题:即使其他线程修改了 flg 的值,执行优化后的线程仍使用寄存器中的旧值。

volatile 关键字

volatile 是一个用于修饰变量的关键字,主要用于保证变量的内存可见性禁止指令重排序

保证可见性
每次访问变量必须要重新读取内存,而不会优化到寄存器/缓存中
代码在写⼊ volatile 修饰的变量的时候,
   • 改变线程⼯作内存中volatile变量副本的值
   • 将改变后的副本的值从⼯作内存刷新到主内存
代码在读取 volatile 修饰的变量的时候
   • 从主内存中读取volatile变量的最新值到线程的⼯作内存中
   • 从⼯作内存中读取volatile变量的副本

禁止指令重排序

针对被 volatile 修饰的变量必须都要重新读取内存,而不会优化到寄存器/缓存中

  • 原理volatile 变量会插入内存屏障(Memory Barrier),禁止编译器和处理器对指令进行重排序

5. 指令重排序

  • 编译器重排序:编译器为优化性能,可能改变代码的执行顺序。
  • 处理器重排序:处理器为提高指令执行效率,可能对指令进行乱序执行。

2. synchronized 关键字 - 监视器锁 monitor lock

针对线程不安全原因3,使用 加锁 的方式,将非原子指令打包成一个整体,确保同一时间,只有该线程可以执行此非原子指令。

synchronized 关键字是实现线程同步的核心机制之一,它基于 ** 监视器锁(Monitor Lock)** 来保证同一时间只有一个线程可以执行被保护的代码块或方法

1、监视器锁的底层原理

  1. 每个对象都有一个监视器锁
    Java 中每个对象(包括类实例和数组)都关联着一个监视器锁(也称为内部锁或互斥锁)。当一个线程试图访问被 synchronized 保护的代码时,它必须先获取该对象的监视器锁。

  2. 锁的获取与释放

    • 获取锁:线程进入 synchronized 代码块前,必须获取对象的监视器锁。如果锁已被其他线程持有,则当前线程会被阻塞,进入锁的等待队列。
    • 释放锁:线程退出 synchronized 代码块时,会自动释放监视器锁,唤醒等待队列中的其他线程竞争锁。
  3. JVM 实现
    监视器锁的实现依赖于对象头中的 Mark Word。当对象被锁定时,Mark Word 会存储指向锁记录的指针,不同状态(偏向锁、轻量级锁、重量级锁)下的存储结构不同

2. synchronized 的使用方式

1. 同步实例方法(锁对象为 this)

使用当前对象实例(this)作为锁

直接修饰普通⽅法

public class SynchronizedDomo {public synchronized void method(){//...}
}
  • 锁对象:隐式使用当前对象实例(this)。
  • 作用范围:整个方法体。
  • 字节码层面:JVM 使用 ACC_SYNCHRONIZED 标志来标记方法,当线程调用该方法时,会自动获取锁并在方法退出时释放锁。

同步代码块

public class SynchronizedDomo {public void method() {synchronized (this) {// 同步代码}}
}
  • 锁对象:显式指定为 this(当前对象实例)。
  • 作用范围:仅包含在 {} 内的代码。
  • 字节码层面:使用 monitorenter 和 monitorexit 指令实现锁的获取和释放。
2. 同步静态方法(锁对象为类的 Class 对象)

使用类的 Class 对象(即 SynchronizedDomo1.class)作为锁

synchronized 修饰静态方法

public class SynchronizedDomo {public static synchronized void method(){//...}
}
  • 锁对象:隐式使用当前类的 Class 对象(如 SynchronizedDomo1.class)。
  • 作用范围:整个静态方法体。
  • 字节码层面:JVM 使用 ACC_SYNCHRONIZED 标志来标记静态方法,当线程调用该方法时,会自动获取类的 Class 对象锁并在方法退出时释放锁。

同步静态代码块

public class SynchronizedDomo {public static void method() {// 反射 类名.class 获取当前类的 class 对象synchronized (SynchronizedDomo1.class) {// 同步代码}}
}
  • 锁对象:显式指定为当前类的 Class 对象。
  • 作用范围:仅包含在 {} 内的代码。
  • 字节码层面:使用 monitorenter 和 monitorexit 指令实现锁的获取和释放。
3. 同步代码块,指定锁对象(locker)
public class SynchronizedDomo1 {//创建锁对象(锁对象可以是任意Object)private Object locker = new Object();public void method(){synchronized (locker){//...}}
}
    代码实例
    public class ThreadDomo3 {public static int count = 0; //共享变量public static void main(String[] args) throws InterruptedException {//创建对象(任意)作为锁对象Object locker = new Object();Thread t1 = new Thread(()->{for(int i=0;i<50000;i++){// 使用locker作为锁,进入同步块前会获取锁synchronized (locker){count++;}// 退出同步块时自动释放锁}});Thread t2 = new Thread(()->{for(int i=0;i<50000;i++){// 使用locker作为锁,进入同步块前会获取锁synchronized (locker){ count++;} // 退出同步块时自动释放锁//count++;}});// 启动两个线程t1.start();t2.start();// 主线程等待两个线程执行完毕t1.join();t2.join();System.out.println(count);//100000}
    }

    3. 互斥

    • 互斥 指同一时间只允许一个线程访问共享资源,其他线程必须等待。

    • 通过 锁(Lock) 或 同步机制(如 synchronized)实现。

    作用

    • 保证原子性:防止多个线程同时修改共享数据导致的数据不一致。
    • 维护可见性:确保一个线程对共享变量的修改能被其他线程正确看到。
    • 防止多个线程同时修改共享数据(如 count++),造成线程不安全

    4. 锁竞争

    锁竞争是指多个线程同时尝试获取同一把锁时发生的竞争现象。当锁被一个线程持有时,其他线程必须等待,从而导致线程阻塞上下文切换,降低程序性能。

    竞争程度表现解决方案
    低竞争线程偶尔阻塞,性能影响小无优化必要
    高竞争大量线程阻塞,CPU空转减小锁粒度、无锁算法

    在上述代码中,两个线程访问共享资源 count, t1 线程进行了同步保护,t2 线程直接访问,就不会形成锁竞争,t2 线程可能看不到 t1 线程对 count 的修改,count ++的原子性被破坏,造成线程不安全。

         

    5. 可重入

    可重入是指同一个线程可以多次获取同一把锁而不会被阻塞可重入锁会记录锁的持有线程和重入次数,当线程退出同步块时,只有重入次数降为 0 才会真正释放锁。

    • 实现原理

      • synchronized 通过 锁计数器 记录重入次数。

      • ReentrantLock 通过 getHoldCount() 获取重入次数。

    Java 中的可重入锁

    • synchronized 关键字:隐式支持可重入。
    • ReentrantLock:显式支持可重入,可通过 lock() 和 unlock() 方法控制。
    public class SynchronizedDomo4 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t = new Thread(()->{// 真正加锁,同时记录锁的持有线程synchronized(locker){ // 第一次获取locker锁,锁计数器为1count++;synchronized (locker){ // 第二次获取同一个locker锁,锁计数器为2count++;synchronized (locker){ // 第三次获取同一个locker锁,锁计数器为3count++;}//解锁,锁计数器为2}//解锁,锁计数器为1}//真正解锁,锁计数器为0});t.start();t.join();System.out.println(count);//3}
    }
    public class SynchronizedDomo5 {public void A(){synchronized (this){// 子线程获取 this 锁,锁计数器+1 → 1B();}}public void B(){C();}public void C(){D();}public void D(){synchronized (this){// 子线程再次获取 this 锁,计数+1 → 2(已持有锁,可重入)System.out.println("hello");}}public static void main(String[] args) throws InterruptedException {SynchronizedDomo5 s = new SynchronizedDomo5();Thread t = new Thread(()->{s.A();});t.start();}
    }

    虽然 A() 和 D() 都使用 synchronized (this) 加锁,但由于锁是可重入的,同一个线程可以在持有锁的状态下嵌套调用其他同步方法,不会导致死锁。

    概念互斥(Mutual Exclusion)锁竞争(Lock Contention)可重入性(Reentrancy)
    目标保护共享资源减少锁冲突避免自我阻塞
    实现手段锁机制锁优化或无锁算法锁计数器
    关联性互斥导致锁竞争高竞争降低性能可重入减少死锁
    关键点同一时间只有一个线程能访问共享资源。多个线程争夺同一把锁,导致阻塞和上下文切换。同一个线程可多次获取同一把锁。

    6. 死锁

    两个或多个线程互相持有对方需要的资源,导致所有线程都无法继续执行的状态。

    死锁的四个必要条件(Coffman条件)

    1. 互斥条件:资源不能被共享,同一时间只能被一个线程占用。
    2. 占有并等待:线程至少已经持有一个资源,同时请求其他线程持有的资源。
    3. 不可抢占:线程已获得的资源不能被其他线程强行抢占,只能自己释放。
    4. 循环等待:存在一个线程的循环等待链,每个线程都在等待下一个线程所占用的资源。

    public class SynchronizedDomo6 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{synchronized (locker1){//休眠1s,为线程2争取获得locker2的时间try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//尝试获取locker2synchronized (locker2){System.out.println("t1");}}});Thread t2 = new Thread(()->{synchronized (locker2){//休眠1s,为线程1争取获得locker1的时间try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//尝试获取locker1synchronized (locker1){System.out.println("t2");}}});t1.start();t2.start();}
    }

     

    • t1 先获取 locker1 锁,然后休眠 1 秒,在这 1 秒内,t2 有机会获取 locker2 锁。
    • t2 先获取 locker2 锁,然后休眠 1 秒,在这 1 秒内,t1 持有 locker1 锁。
    • 当 t1 休眠结束后尝试获取 locker2 锁时,t2 已持有 locker2 锁;而当 t2 休眠结束后尝试获取 locker1 锁时,t1 已持有 locker1 锁。
    • 这样就形成了 t1 等待 t2 释放 locker2 锁,t2 等待 t1 释放 locker1 锁的情况,两个线程相互等待对方释放锁,从而导致死锁。程序卡死。
    如何避免死锁
    1. 破坏互斥条件:不是所有资源都能这样做(如打印机必须互斥使用)

    2. 破坏占有并等待

      • 线程在开始时就获取所有需要的锁,否则不获取任何锁。

      • 使用tryLock()等非阻塞获取锁的方法

    3. 破坏不可抢占条件

      • 使用可响应中断的锁(如ReentrantLock

      • 设置获取锁的超时时间

    4. 破坏循环等待条件

      • 对资源进行排序,按固定顺序获取锁

      • 使用资源分配图算法检测

    7. volatile vs synchronized

    特性volatilesynchronized
    可见性✅ 保证可见性✅ 保证可见性
    原子性❌ 不保证原子性(如 i++✅ 保证原子性
    指令重排序✅ 禁止重排序✅ 禁止重排序
    性能开销较小,适合轻量级同步开销较大,适合重量级同步
    使用场景状态标志、单次初始化、禁止重排序复合操作、方法或代码块同步

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

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

    相关文章

    Vue 3.0中响应式依赖和更新

    响应式依赖和更新是Vue 3.0中最重要的机制&#xff0c;其核心代码如下&#xff0c;本文将结合代码对这个设计机制作出一些解释。 // 全局依赖存储&#xff1a;WeakMap<target, Map<key, Set<effect>>> const targetMap new WeakMap();// 当前活动的副作用函…

    一、内存调优

    一、内存调优 什么是内存泄漏 监控Java内存的常用工具 内存泄露的常见场景 内存泄露的解决方案 内存泄露与内存溢出的区别 内存泄露&#xff1a;在Java中如果不再使用一个对象&#xff0c;但是该对象依然在GC ROOT的引用链上&#xff0c;这个对象就不会被垃圾回收器回收&…

    Linux /etc/rc.d/init.d/

    在传统的 SysV init 系统中&#xff0c;服务启动脚本通常位于 /etc/rc.d/init.d/ 目录下。这些脚本可以直接执行以启动、停止或重启服务&#xff0c;并且可以接受参数如 start, stop, status 等。 如果你想知道位于 /etc/rc.d/init.d/ 目录下的某个脚本文件实际上指向哪里,如果…

    S7 200 smart连接Profinet转ModbusTCP网关与西门子1200PLC配置案例

    控制要求&#xff1a;使用MODBUSTCP通信进行两台PLC之间的数据交换&#xff0c;由于改造现场不能改动程序&#xff0c;只留出了对应的IQ地址。于是客户决定使用网关进行通讯把数据传到plc。 1、读取服务器端40001~40005地址中的数据&#xff0c;放入到VW200~VW208中&#xff1…

    打破传统仓库管理困局:WMS如何重构出入库全流程

    引言 在制造业与零售业高速发展的今天&#xff0c;仓库管理仍普遍面临效率低、错发漏发频发、库存数据滞后等痛点。人工登记导致30%的错单率&#xff0c;货位混乱让拣货耗时增加50%&#xff0c;而账实不符引发的二次采购成本更吞噬着企业利润。如何突破传统管理桎梏&#xff1…

    Text2SQL在Spark NLP中的实现与应用:将自然语言问题转换为SQL查询的技术解析

    概述 SQL 仍然是当前行业中最受欢迎的技能之一 免责声明&#xff1a;Spark NLP 中的 Text2SQL 注释器在 v3.x&#xff08;2021 年 3 月&#xff09;中已被弃用&#xff0c;不再使用。如果您想测试该模块&#xff0c;请使用 Spark NLP for Healthcare 的早期版本。 自新千年伊…

    微服务项目->在线oj系统(Java版 - 5)

    相信自己,终会成功 微服务代码: lyyy-oj: 微服务 目录 C端代码 用户题目接口 修改后用户提交代码(应用版) 用户提交题目判题结果 代码沙箱 1. 代码沙箱的核心功能 2. 常见的代码沙箱实现方式 3. 代码沙箱的关键问题与解决方案 4. 你的代码如何与沙箱交互&#xff1f; …

    Vue3 Element Plus 中el-table-column索引使用问题

    在 Element Plus 的 el-table 组件中&#xff0c;使用 scope.index 是不准确的。正确的索引属性应该是 scope.$index。你的代码需要调整为&#xff1a; vue 复制 下载 <el-button type"primary" size"default" text click"onModifyClick(scope…

    Ubuntu20.04下使用dpkg方式安装WPS后,将WPS改为中文界面方法

    Ubuntu20.04下使用dpkg方式安装WPS后&#xff0c;将WPS改为中文界面方法 说明方法 说明 Ubuntu20.04下使用dpkg方式安装WPS后&#xff0c;打开WPS后&#xff0c;发现界面是英文的&#xff0c;如有需要可以按照下面的方法将其改为中文界面。 方法 cd /opt/kingsoft/wps-offic…

    【​​HTTPS基础概念与原理​】​​HTTPS vs HTTP:为什么现代网站必须用HTTPS?

    以下是关于 HTTPS vs HTTP 的详细对比分析&#xff0c;涵盖安全性、性能差异及SEO影响&#xff0c;帮助您全面理解为何现代网站必须采用HTTPS&#xff1a; 一、安全性对比&#xff1a;HTTPS 如何解决 HTTP 的致命缺陷 1. HTTP 的安全隐患 • 明文传输&#xff1a;HTTP 数据以明…

    算法刷题(Java与Python)1.二分查找

    目录 二分查找 思路 总体 细节 问题一&#xff0c;为什么循环的条件是left<right ,为什么要有等号呢 问题二&#xff0c;为什么中间值是left (right - left) / 2 问题三&#xff0c;为什么最后返回的是左边的值呢 情况 1&#xff1a;target 存在于数组中 情况 2&a…

    芯片生态链深度解析(二):基础设备篇——人类精密制造的“巅峰对决”

    【开篇&#xff1a;设备——芯片工业的“剑与盾”】 当ASML的EUV光刻机以每秒5万次激光脉冲在硅片上雕刻出0.13nm精度的电路&#xff08;相当于在月球表面精准定位一枚二维码&#xff09;&#xff0c;当国产28nm光刻机在华虹产线实现“从0到1”的突破&#xff0c;这场精密制造…

    MongoTemplate 基础使用帮助手册

    前言 MongoDB 是一种流行的 NoSQL 数据库&#xff0c;适合存储大量的非结构化数据。MongoTemplate 是 Spring Data MongoDB 中的一个核心组件&#xff0c;它提供了一组丰富的 API 来与 MongoDB 进行交互。它封装了许多常见的数据库操作&#xff0c;使开发者能够轻松执行 CRUD 操…

    psotgresql18 源码编译安装

    环境&#xff1a; 系统&#xff1a;centos7.9 数据库&#xff1a;postgresql18beta1 #PostgreSQL 18 已转向 DocBook XML 构建体系&#xff08;SGML 未来将被弃用&#xff09;。需要安装 XML 工具链&#xff0c;如下&#xff1a; yum install -y docbook5-style-xsl libxsl…

    C++编程起步项目

    员工信息管理系统 需求 Employee.h #pragma once#include<iostream> #include<string>using namespace std;class Employee { public:int id; // 编号string name; // 姓名string position; // 岗位int deptId; // 部门编号Employee();Employee(int id, string n…

    Linux的MySQL头文件和找不到头文件问题解决

    头文件 #include <iostream> #include <mysql_driver.h> #include <mysql_connection.h> #include <cppconn/statement.h> #include <cppconn/resultset.h> #include <cppconn/prepared_statement.h> #include <cppconn/exception.h&g…

    [ linux-系统 ] 命令行参数 | 环境变量

    命令行参数 命令行参数是指用户在启动程序时通过命令行传递给程序的参数。这些参数可以用于控制程序的行为、传递输入数据或配置选项。 在 C/C 中&#xff0c;命令行参数通过 main 函数的参数传递 命令行参数列表 argc:参数的个数 argv[]&#xff1a;参数的清单 为什么要…

    新书速览|鸿蒙HarmonyOS NEXT开发之路 卷2:从入门到应用篇

    《鸿蒙HarmonyOS NEXT开发之路 卷2&#xff1a;从入门到应用篇》 01 本书内容 《鸿蒙HarmonyOS NEXT开发之路 卷2&#xff1a;从入门到应用篇》是一本深度聚焦HarmonyOS NEXT应用开发的全方位指导书&#xff0c;内容遵循由浅入深的原则展开。全书分为基础知识、应用开发进阶和…

    经典密码学和现代密码学的结构及其主要区别(1)凯撒密码——附py代码

    密码学是一门通过使用代码和密码来保护信息的艺术与科学&#xff0c;其历史可以追溯到数千年前。古典密码学代表了这一古老学科早期的篇章。早在计算机和现代加密算法出现之前&#xff0c;历史上的各个文明就依靠巧妙的方法来保护机密、安全通信以及获取战略优势。 古典密码学…

    Python60日基础学习打卡D30

    回顾&#xff1a; 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑&#xff1a;找到根目录&#xff08;python解释器的目录和终端的目录不一致&#xff09; # 直接导入 from random import randint print(randint(1, 10)) # 导入自定义库 import module m…