Java 多线程编程详解

news/2025/10/21 13:45:43/文章来源:https://www.cnblogs.com/codingkf/p/19154842

Java 多线程编程详解

Java 多线程编程是实现并发任务的核心技术,通过充分利用 CPU 多核资源,可显著提升程序的执行效率(如并行处理数据、异步响应请求等)。本文从线程基础、创建方式、同步机制、线程通信到高级工具类,全面解析 Java 多线程编程的核心知识点与实战技巧。

一、线程基础:什么是线程?为什么需要多线程?

在 Java 中,进程是程序的一次执行过程(如运行中的 JVM 实例),而线程是进程内的最小执行单元(一个进程可包含多个线程,共享进程资源)。多线程的核心价值在于:
 
  • 提高资源利用率:当一个线程因 IO 操作(如读写文件、网络请求)阻塞时,其他线程可继续执行,避免 CPU 空闲。
  • 提升响应速度:如 GUI 程序中,用后台线程处理数据,主线程保持界面响应(避免界面卡顿)。

线程与进程的核心区别

维度进程线程
资源占用 独立内存空间、文件描述符 共享进程内存空间、资源
切换开销 大(需切换内存映射等) 小(仅切换寄存器、栈等)
通信方式 复杂(如管道、Socket) 简单(直接读写共享变量)

线程的生命周期

Java 线程有 5 种状态,状态转换是多线程调试的核心依据:
 
  1. 新建(New):线程对象创建后(如 new Thread()),未调用 start() 时的状态。
  2. 就绪(Runnable):调用 start() 后,线程进入 “可运行” 状态,等待 CPU 调度(可能处于运行中或等待调度)。
  3. 运行(Running):CPU 调度线程执行 run() 方法,此时处于运行状态。
  4. 阻塞(Blocked/Waiting/Timed Waiting):线程暂停执行,如等待锁(Blocked)、调用 wait()Waiting)、sleep(1000)Timed Waiting)。
  5. 死亡(Terminated)run() 方法执行完毕,或因异常终止。

二、线程创建:3 种核心方式与对比

Java 提供 3 种创建线程的方式,各有适用场景,需根据是否需要返回值、是否继承类等需求选择。

1. 继承 Thread 类(无返回值)

Thread 类是线程的核心类,继承它并重写 run() 方法(线程执行体),调用 start() 启动线程(不可直接调用 run(),否则会作为普通方法执行)。
 
// 1. 继承 Thread 类
class MyThread extends Thread {@Overridepublic void run() {  // 线程执行体for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
}// 2. 使用线程
public class ThreadDemo {public static void main(String[] args) {MyThread t1 = new MyThread();t1.setName("线程1");  // 设置线程名t1.start();  // 启动线程(进入就绪状态,等待CPU调度)// 主线程执行for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
}
 
 
输出(顺序不确定,因 CPU 调度)
 
main:0
线程1:0
main:1
线程1:1
...
 
 
缺点:Java 是单继承,继承 Thread 后无法再继承其他类,灵活性低。

2. 实现 Runnable 接口(无返回值,推荐)

Runnable 是函数式接口(仅 run() 方法),实现它后,将实例传入 Thread 构造器,通过 Thread 启动线程。解决了单继承限制,是最常用的方式。
 
// 1. 实现 Runnable 接口
class MyRunnable implements Runnable {@Overridepublic void run() {  // 线程执行体for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
}// 2. 使用线程
public class RunnableDemo {public static void main(String[] args) {// 创建 Runnable 实例MyRunnable task = new MyRunnable();// 传入 Thread 构造器,启动线程Thread t2 = new Thread(task, "线程2");  // 直接指定线程名t2.start();// 主线程执行for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
}
 
 
优势
 
  • 可多线程共享一个 Runnable 实例(适合多线程处理同一份资源,如卖票系统)。
  • 避免单继承限制,更灵活。

3. 实现 Callable 接口(有返回值,支持异常)

Callable 接口(Java 5+)与 Runnable 类似,但:
 
  • 有返回值(泛型 V);
  • 可抛出受检异常;
  • 需配合 Future 或 FutureTask 获取结果。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;// 1. 实现 Callable 接口(泛型指定返回值类型)
class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {  // 线程执行体,有返回值int sum = 0;for (int i = 1; i <= 10; i++) {sum += i;}return sum;  // 返回 1+2+...+10 的结果}
}// 2. 使用线程
public class CallableDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建 Callable 实例MyCallable task = new MyCallable();// 包装为 FutureTask(实现了 Future 和 Runnable 接口)FutureTask<Integer> futureTask = new FutureTask<>(task);// 传入 Thread 启动new Thread(futureTask, "计算线程").start();// 主线程获取结果(get() 会阻塞,直到子线程执行完毕)System.out.println("1到10的和:" + futureTask.get());  // 输出:55}
}
 
 
适用场景:需要线程执行结果的场景(如并行计算汇总结果)。

三、线程同步:解决并发安全问题

多线程共享资源时,若多个线程同时读写共享变量,可能导致数据不一致(如卖票系统中多线程同时减库存,出现超卖)。线程同步的核心是 “保证同一时间只有一个线程访问共享资源”。

1. 共享资源问题示例(未同步)

// 共享资源:票池
class Ticket {private int count = 10;  // 10张票// 卖票方法(未同步)public void sell() {if (count > 0) {// 模拟网络延迟(放大并发问题)try { Thread.sleep(100); } catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName() + "卖出1张,剩余:" + (--count));}}
}// 多线程卖票
public class UnsafeDemo {public static void main(String[] args) {Ticket ticket = new Ticket();  // 共享一个票池// 3个线程同时卖票new Thread(() -> { for (int i = 0; i < 5; i++) ticket.sell(); }, "窗口1").start();new Thread(() -> { for (int i = 0; i < 5; i++) ticket.sell(); }, "窗口2").start();new Thread(() -> { for (int i = 0; i < 5; i++) ticket.sell(); }, "窗口3").start();}
}
 
 
可能的错误输出(出现负数,超卖):
窗口1卖出1张,剩余:9
窗口2卖出1张,剩余:8
窗口3卖出1张,剩余:7
...
窗口1卖出1张,剩余:-1  // 错误!
 

2. synchronized 关键字(隐式锁)

synchronized 是 Java 内置的同步机制,通过 “锁” 保证代码块 / 方法的原子性(同一时间只有一个线程执行)。有两种用法:
(1)同步方法(锁是当前对象 this
在方法声明上添加 synchronized,锁对象为当前实例(this)。
class Ticket {private int count = 10;// 同步方法(锁是 this)public synchronized void sell() {if (count > 0) {try { Thread.sleep(100); } catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName() + "卖出1张,剩余:" + (--count));}}
}
 
(2)同步代码块(锁是指定对象)
更灵活,可指定锁对象(如 this、类对象、自定义对象),只同步关键代码(减少锁竞争)。
 
class Ticket {private int count = 10;private Object lock = new Object();  // 自定义锁对象public void sell() {// 同步代码块(锁是 lock 对象)synchronized (lock) {if (count > 0) {try { Thread.sleep(100); } catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName() + "卖出1张,剩余:" + (--count));}}}
}
 
 
注意
 
  • 锁对象必须是同一个(多线程竞争同一把锁才有效)。
  • 同步会降低效率(因线程阻塞等待锁),需最小化同步范围(只锁必要代码)。

3. Lock 接口(显式锁,Java 5+)

java.util.concurrent.locks.Lock 是更灵活的同步机制,需手动获取和释放锁(lock() 和 unlock()),推荐配合 try-finally 使用(确保锁释放)。
 
常用实现类:ReentrantLock(可重入锁,支持公平锁 / 非公平锁)。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class Ticket {private int count = 10;// 创建锁对象(true 表示公平锁:按线程等待顺序获取锁,默认非公平锁)private Lock lock = new ReentrantLock(true);public void sell() {lock.lock();  // 获取锁try {if (count > 0) {try { Thread.sleep(100); } catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName() + "卖出1张,剩余:" + (--count));}} finally {lock.unlock();  // 释放锁(必须在 finally 中,避免异常导致锁未释放)}}
}
 
 
Lock vs synchronized
 
特性synchronizedLock
锁获取 / 释放 隐式(自动获取 / 释放) 显式(lock()/unlock()
公平锁 不支持 支持(ReentrantLock(true)
中断响应 不支持(不可中断等待锁的线程) 支持(lockInterruptibly()
超时机制 不支持 支持(tryLock(long, TimeUnit)

4. volatile 关键字(保证可见性)

volatile 用于修饰共享变量,保证多线程间的可见性(一个线程修改后,其他线程立即看到最新值),但不保证原子性(不能替代 synchronized 或 Lock)。
 
适用场景:单线程写、多线程读的变量(如状态标记)。
 
class Flag {private volatile boolean isStop = false;  // volatile 保证可见性public void setStop(boolean stop) {isStop = stop;}public void run() {while (!isStop) {  // 多线程读取时,能立即看到 isStop 的修改System.out.println("运行中...");}System.out.println("已停止");}
}public class VolatileDemo {public static void main(String[] args) throws InterruptedException {Flag flag = new Flag();Thread t = new Thread(flag::run);t.start();// 主线程 2 秒后修改状态Thread.sleep(2000);flag.setStop(true);}
}
 

四、线程通信:协作完成任务

多线程常需协作(如生产者生产数据后通知消费者消费),Java 提供两种核心通信方式:

1. wait()/notify()/notifyAll()(基于 synchronized

这三个方法是 Object 类的 native 方法,必须在同步代码块 / 方法中使用(否则抛 IllegalMonitorStateException),作用是:
 
  • wait():释放当前锁,进入等待状态,直到被 notify() 唤醒。
  • notify():随机唤醒一个等待该锁的线程。
  • notifyAll():唤醒所有等待该锁的线程。
 
生产者 - 消费者模型示例
// 共享缓冲区
class Buffer {private int data;private boolean hasData = false;  // 是否有数据// 生产者放入数据public synchronized void put(int num) throws InterruptedException {while (hasData) {  // 若有数据,等待消费者取走wait();  // 释放锁,进入等待}data = num;hasData = true;System.out.println("生产者放入:" + num);notify();  // 唤醒消费者}// 消费者取出数据public synchronized int take() throws InterruptedException {while (!hasData) {  // 若无数据,等待生产者放入wait();  // 释放锁,进入等待}hasData = false;System.out.println("消费者取出:" + data);notify();  // 唤醒生产者return data;}
}// 生产者线程
class Producer implements Runnable {private Buffer buffer;// 构造器传入缓冲区public Producer(Buffer buffer) { this.buffer = buffer; }@Overridepublic void run() {for (int i = 1; i <= 5; i++) {try {buffer.put(i);Thread.sleep(500);  // 模拟生产耗时} catch (InterruptedException e) {}}}
}// 消费者线程
class Consumer implements Runnable {private Buffer buffer;public Consumer(Buffer buffer) { this.buffer = buffer; }@Overridepublic void run() {for (int i = 1; i <= 5; i++) {try {buffer.take();Thread.sleep(1000);  // 模拟消费耗时} catch (InterruptedException e) {}}}
}// 测试
public class ProducerConsumer {public static void main(String[] args) {Buffer buffer = new Buffer();new Thread(new Producer(buffer)).start();new Thread(new Consumer(buffer)).start();}
}
 
 
输出
 
生产者放入:1
消费者取出:1
生产者放入:2
消费者取出:2
...
 

2. Condition 接口(基于 Lock,更灵活)

Condition 是 Lock 的配套接口,通过 Lock.newCondition() 创建,替代 wait()/notify(),支持多条件等待(一个锁可关联多个 Condition,实现更精细的线程唤醒)。
 
核心方法:await()(替代 wait())、signal()(替代 notify())、signalAll()(替代 notifyAll())。

五、线程池:高效管理线程资源

频繁创建 / 销毁线程会消耗大量资源(线程创建需分配栈空间等),线程池通过复用线程减少开销,是生产环境的推荐方式(Java 5+ 提供 Executor 框架)。

1. 线程池核心参数(ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,        // 核心线程数(常驻线程,即使空闲也不销毁)int maximumPoolSize,     // 最大线程数(核心线程+临时线程的上限)long keepAliveTime,      // 临时线程空闲超时时间(超时后销毁)TimeUnit unit,           // keepAliveTime 的单位(如 SECONDS)BlockingQueue<Runnable> workQueue,  // 任务队列(核心线程满时,新任务入队)ThreadFactory threadFactory,        // 线程工厂(自定义线程创建,如命名)RejectedExecutionHandler handler    // 拒绝策略(任务队列满+线程达最大时的处理方式)
)
 

2. 常见线程池(Executors 工具类创建)

Executors 提供了 4 种预定义线程池,覆盖多数场景:
 
线程池类型特点适用场景
FixedThreadPool 核心线程数 = 最大线程数,无临时线程,队列无界(LinkedBlockingQueue 任务数量固定,需长期运行的任务
CachedThreadPool 核心线程数 = 0,最大线程数 = Integer.MAX_VALUE,队列是同步队列 短期、轻量任务(如临时请求)
SingleThreadExecutor 核心线程数 = 1,最大线程数 = 1,队列无界 需顺序执行的任务(如日志记录)
ScheduledThreadPool 支持定时 / 周期性执行任务 定时任务(如心跳检测)
 
示例:FixedThreadPool 使用
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolDemo {public static void main(String[] args) {// 创建固定 3 个线程的线程池ExecutorService pool = Executors.newFixedThreadPool(3);// 提交 5 个任务for (int i = 0; i < 5; i++) {int taskId = i;pool.submit(() -> {  // 提交 Runnable 任务System.out.println(Thread.currentThread().getName() + "执行任务:" + taskId);try { Thread.sleep(1000); } catch (InterruptedException e) {}});}pool.shutdown();  // 关闭线程池(已提交任务执行完毕后关闭)}
}
 
 
输出(3 个线程复用执行 5 个任务):
 
pool-1-thread-1执行任务:0
pool-1-thread-2执行任务:1
pool-1-thread-3执行任务:2
pool-1-thread-1执行任务:3  // 线程1复用
pool-1-thread-2执行任务:4  // 线程2复用
 

3. 拒绝策略(任务无法处理时)

当任务队列满且线程数达最大值时,线程池会触发拒绝策略,ThreadPoolExecutor 提供 4 种默认策略:
 
拒绝策略行为
AbortPolicy(默认) 抛 RejectedExecutionException 异常(推荐,及时发现问题)
CallerRunsPolicy 由提交任务的线程(如主线程)执行任务(降低新任务提交速度)
DiscardPolicy 直接丢弃新任务(无提示,不推荐)
DiscardOldestPolicy 丢弃队列中最旧的任务,再尝试提交新任务

六、高级并发工具类(java.util.concurrent

JDK 提供多种工具类简化复杂并发场景,以下是最常用的 3 种:

1. CountDownLatch(倒计时器)

让一个线程等待其他多个线程完成后再执行(如主线程等待所有子线程处理完数据后汇总)。
 
import java.util.concurrent.CountDownLatch;public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {int threadCount = 3;CountDownLatch latch = new CountDownLatch(threadCount);  // 计数 3for (int i = 0; i < threadCount; i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + "完成任务");latch.countDown();  // 计数减1}, "子线程" + i).start();}latch.await();  // 主线程等待,直到计数为0System.out.println("所有子线程完成,主线程开始汇总");}
}
 

2. CyclicBarrier(循环屏障)

让多个线程到达屏障点后再一起继续执行(如多个线程先各自加载数据,全部加载完后再一起处理)。
 
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierDemo {public static void main(String[] args) {int threadCount = 3;// 屏障点:3个线程到达后,执行 Runnable(可选)CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {System.out.println("所有线程已到达,开始处理数据");});for (int i = 0; i < threadCount; i++) {new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + "加载数据中...");Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + "到达屏障点");barrier.await();  // 等待其他线程System.out.println(Thread.currentThread().getName() + "继续执行");} catch (Exception e) {}}, "线程" + i).start();}}
}
 

3. Semaphore(信号量)

控制同时访问某个资源的线程数量(如限制并发连接数)。
 
import java.util.concurrent.Semaphore;public class SemaphoreDemo {public static void main(String[] args) {int permits = 2;  // 允许 2 个线程同时访问Semaphore semaphore = new Semaphore(permits);for (int i = 0; i < 5; i++) {new Thread(() -> {try {semaphore.acquire();  // 获取许可(若满则等待)System.out.println(Thread.currentThread().getName() + "获取资源,开始操作");Thread.sleep(1000);  // 模拟操作耗时} catch (InterruptedException e) {} finally {semaphore.release();  // 释放许可System.out.println(Thread.currentThread().getName() + "释放资源");}}, "用户" + i).start();}}
}
 

七、线程安全与最佳实践

  1. 避免死锁:死锁是多线程因互相等待对方释放锁而卡死的状态,预防措施:
    • 按固定顺序获取锁(如所有线程先锁 A 再锁 B);
    • 使用 tryLock(timeout) 设置超时,避免无限等待。
  2. 减少锁竞争
    • 最小化同步范围(只锁必要代码);
    • 用 ConcurrentHashMap 等并发集合替代同步容器(如 Hashtable)。
  3. 优先使用线程池:避免手动创建线程,通过线程池管理资源,控制并发量。
  4. 慎用 ThreadLocalThreadLocal 为每个线程提供独立变量副本(如存储用户会话),但需注意:
    • 线程池环境下,线程复用可能导致 ThreadLocal 变量残留,需手动 remove()
    • 避免存储大对象,防止内存泄漏。

总结

Java 多线程编程的核心是并发协作与资源安全,需掌握:
 
  • 线程创建的 3 种方式(Thread/Runnable/Callable);
  • 同步机制(synchronized/Lock/volatile)解决线程安全问题;
  • 线程通信(wait()/notify()/Condition)实现任务协作;
  • 线程池(ThreadPoolExecutor)高效管理线程资源;
  • 高级工具类(CountDownLatch/CyclicBarrier/Semaphore)简化复杂场景。
 
实际开发中,需结合业务场景选择合适的技术,同时重视线程安全与性能平衡,避免过度同步或资源浪费。

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

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

相关文章

[LangChian] 05.结构化提示词

在跟各种 聊天模型 交互的时候,在构建聊天信息时,不仅仅包含了像上文中的文本内容,也需要与每条消息关联的角色信息。 例如这条信息是由 人类、AI、还是给 chatbot 指定的 system 信息,这种结构化的消息输入有助于…

C#获取文件md5码

C#获取文件md5码代码/// <summary> /// 原文链接:https://blog.csdn.net/makenothing/article/details/39493779 /// </summary> /// <param name="fileName"></param> /// <re…

2025年10月防腐木凉亭厂家对比评测榜:江西纳美领衔五强深度解析

一、引言 防腐木凉亭作为市政园林、地产配套与私家庭院的高频采购单元,其耐候性、结构安全与景观表现力直接决定项目整体品质与后期维护成本。2025年10月,北方进入施工收尾、南方仍处旺季的交叉时段,采购者面临“工…

2025通风天窗实力厂家推荐,正鑫专业制造与定制服务保障

2025通风天窗实力厂家推荐,正鑫专业制造与定制服务保障 当前通风天窗领域的技术挑战与行业现状 工业厂房通风系统作为保障生产环境安全的重要设施,其技术性能直接影响着企业的生产效率和员工健康。在当前的通风天窗、…

2025工业清洗设备实力厂家推荐:无锡瑞仕达精密CNC高压去毛刺清洗机

2025工业清洗设备实力厂家推荐:无锡瑞仕达精密CNC高压去毛刺清洗机 在工业制造领域,清洗环节一直是影响产品质量和生产效率的关键工序。随着制造业向高精度、高质量方向发展,传统清洗方式已难以满足复杂零部件的高清…

2025年10月治鼻炎产品推荐:权威对比评测榜助您精准选购

一、引言 秋季花粉、尘螨浓度在10月达到年度次高峰,鼻炎患者鼻塞、喷嚏、流涕症状集中爆发,创业者若计划布局鼻腔护理赛道、药店采购者欲更新货架、消费者希望快速缓解不适,都面临同一痛点:如何在同质化严重的市场…

git提PR时很多别人的commit,清理多余的commit

在自己的分支上git pull后git push提PR时,有时除了自己的commit,会把很多别人的commit也带进去,因为自己的分支落后主分支太多 如何把提的PR里很多别人的commit清除掉?git checkout mybranch git fetch origin mai…

Visual Studio 使用小知识记录

Visual Studio 使用小知识记录 使用 CodeLens 显示类,函数或者属性的引用和修改信息 在 "选项" -> "文本编辑器" -> "所有语言"-> "CodeLens" 设置启用codeLens功能…

2025数控锯床厂家推荐无锡正川,专业立式锯床制造企业

2025数控锯床行业深度解析:专业立式锯床的技术革新与市场前景 当前锯床行业面临的技术挑战与突破 随着制造业向智能化、精密化方向快速发展,数控锯床作为金属加工领域的关键设备,正面临着前所未有的技术挑战。根据行…

DeepSeek-OCR:让 AI “一眼看懂” 的黑科技

一张包含1000个文字的文档图片,只需要不到100个视觉token就能精准识别,这就是DeepSeek-OCR带来的革命性突破。最近,DeepSeek-AI团队发布了一款名为DeepSeek-OCR的新模型。这不是一个普通的文字识别工具,而是一种全…

生成一张图,苹果logo是透明冰块,安卓小机器人撒尿到苹果logo,冲出一个豁口

生成一张图,苹果logo是透明冰块,安卓小机器人撒尿到苹果logo,冲出一个豁口安卓机器人身高高于苹果logo,JJ和豁口齐平机器人腿太长了比例不协调

kafka2.8出现NotLeaderOrFollowerException

具体错误信息: org.apache.kafka.common.errors.NotLeaderOrFollowerException: For requests intended only for the leader, this error indicates that the broker is not the current leader. For requests inten…

IEC 61850 ICD文件解析

一、IEC 61850 IEC 61850是电力系统自动化领域的国际通信标准,由国际电工委员会第57技术委员会于2004年颁布。该标准通过定义变电站三层通信架构(站控层、间隔层、过程层)实现智能变电站工程标准化,其核心特点包括…

2025安全光栅厂家推荐安一光电,超薄无盲区设计守护工业安全

2025安全光栅厂家推荐安一光电,超薄无盲区设计守护工业安全 工业安全防护的技术挑战与创新需求 在现代工业生产环境中,安全光栅作为重要的防护设备,面临着日益复杂的技术挑战。随着自动化程度的提高和生产线速度的不…

用poi导入Excel

1每天多努力一点,你将会变得更好。

2025无锡新梅赛智能设备厂家推荐:全自动视觉定位点胶机专业制造商

2025无锡新梅赛智能设备厂家推荐:全自动视觉定位点胶机专业制造商 技术挑战与行业痛点 在现代制造业中,点胶工艺作为精密生产的关键环节,面临着前所未有的技术挑战。随着产品微型化、精密化趋势的加速,传统点胶设备…

2025石头纸设备厂家权威推荐:鼎浩包装科技环保吹塑机制造专家

2025石头纸设备厂家权威推荐:鼎浩包装科技环保吹塑机制造专家 技术挑战与行业现状 在环保材料快速发展的今天,石头纸设备行业面临着多重技术挑战。根据行业数据显示,传统造纸工艺每年消耗大量木材资源,而石头纸技术…

实用指南:【ATBS with Python】QA Chap2 If-else and Flow Control

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

用 Python 轻松克服 PDF 指定页替换为图片的痛点难题

用 Python 轻松克服 PDF 指定页替换为图片的痛点难题2025-10-21 13:21 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; dis…