Java多线程编程初阶指南
目录
一.线程基础概念
线程是什么?
线程与进程对比
为啥要有线程
二.线程实现方式
继承Thread类
实现Runnable接口
常规实现方式
匿名内部类写法
Lambda表达式写法(Java8+)
对比总结
三.Thread 类及常见方法
核心功能
核心构造方法
核心属性获取方法
线程控制核心方法
启动线程:start()
中断控制:interrupt()
线程等待:join()
线程休眠:sleep()
四.线程的状态
NEW(新建状态)
RUNNABLE(可运行状态)
BLOCKED(阻塞状态)
WAITING(无限等待)
TIMED_WAITING(限期等待)
五.线程安全(重点)
什么是线程安全?
原子性(Atomicity)
可见性(Visibility)
有序性(Ordering)
解决之前的线程不安全问题
synchronized 关键字
volatile 关键字
原子性问题解决方案
可见性问题解决方案
有序性问题解决方案
六.wait()与notify()
核心作用解析
1. 线程协调的基石
2. 方法定位
标准使用模板
1. 基础协作模式
2. 生产者-消费者实现
关键使用原则
1. 必须遵守的黄金法则
2. 选择通知策略
3. 超时控制(避免永久阻塞)
七.多线程案例
单例模式
阻塞式队列
定时器
线程池
总结-保证线程安全的思路
一.线程基础概念
线程是什么?
线程是操作系统调度的最小执行单元,属于同一进程的多个线程共享进程资源(内存、文件句柄等),每个线程独立执行代码指令序列。
- 执行流:线程是程序中的独立执行路径,多个线程可并发执行
- 轻量级进程:相比进程,线程更轻量(创建/销毁更快,共享内存空间)
- 主从关系:主线程创建子线程,共同完成任务
线程与进程对比
特性 | 进程 | 线程 |
---|---|---|
资源隔离 | 独立内存空间 | 共享进程内存 |
创建开销 | 高(系统级资源分配) | 低(仅需栈和程序计数器) |
通信效率 | 低(需IPC机制) | 高(直接内存共享) |
上下文切换 | 耗时(内存映射切换) | 快速(仅寄存器切换) |
为啥要有线程
首先, "并发编程" 成为 "刚需". 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源. 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.
其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量. 创建线程比创建进程更快. 销毁线程比销毁进程更快. 调度线程比调度进程更快.
二.线程实现方式
继承Thread类
// 传统继承方式
class CustomThread extends Thread {@Overridepublic void run() {System.out.println("线程ID: " + getId());}
}// 创建启动
new CustomThread().start();
特点:
- 直接继承Thread类
- 通过重写run()方法定义任务
- 适用于简单场景,但破坏了Java单继承特性
实现Runnable接口
常规实现方式
class Task implements Runnable {@Overridepublic void run() {System.out.println("传统Runnable实现");}
}// 创建启动
new Thread(new Task()).start();
匿名内部类写法
// 匿名内部类实现
new Thread(new Runnable() {@Overridepublic void run() {System.out.println("匿名内部类线程");// 可访问外部final变量}
}).start();
优势:
- 适用于一次性任务
- 支持访问外部final变量
- 示例:事件监听等临时性任务
Lambda表达式写法(Java8+)
// Lambda简化实现
new Thread(() -> {System.out.println("Lambda表达式线程");// 支持多行代码
}).start();// 单行语句简写
new Thread(() -> System.out.println("极简Lambda线程")).start();
特点:
- 要求接口是函数式接口(单一抽象方法)
- 自动类型推导
- 代码简洁度提升
- 推荐用于简单任务场景
对比总结
实现方式 | 代码行数 | 可复用性 | 灵活性 | 推荐指数 |
---|---|---|---|---|
继承Thread | 5-10 | 低 | 差 | ★☆☆☆☆ |
常规Runnable | 6-8 | 高 | 中 | ★★★☆☆ |
匿名内部类 | 3-5 | 低 | 高 | ★★★★☆ |
Lambda表达式 | 1-3 | 中 | 极高 | ★★★★★ |
三.Thread 类及常见方法
核心功能
- 线程创建与启动
- 线程状态管理
- 线程优先级控制
- 线程中断处理
- 线程同步协作
核心构造方法
构造方法 | 适用场景 |
---|---|
Thread() | 创建空线程对象 |
Thread(Runnable target) | 标准Runnable实现(推荐) |
Thread(String name) | 创建命名线程 |
Thread(Runnable target, String name) | 命名+Runnable实现 |
Thread(ThreadGroup group, ...) | 线程组管理(高级用法) |
核心属性获取方法
方法 | 返回值类型 | 说明 |
---|---|---|
getId() | long | 获取线程唯一ID |
getName() | String | 获取线程名称 |
getState() | State | 获取线程状态 |
getPriority() | int | 获取优先级(1-10) |
isAlive() | boolean | 判断线程是否存活 |
isInterrupted() | boolean | 中断状态检查(不清除标志) |
线程控制核心方法
启动线程:start()
作用:启动线程执行,使线程进入可运行状态(RUNNABLE)
Thread t = new Thread(task);
t.start(); // 正确启动方式
t.run(); // 错误!在调用线程同步执行
执行流程:
- JVM创建新线程(从NEW状态转换)
- 调用run()方法
- 线程进入RUNNABLE状态
- 由线程调度器分配CPU时间片
关键特性:
- 每个线程只能调用一次start()
- 实际执行顺序由OS调度器决定
- 直接调用run()方法不会创建新线程
中断控制:interrupt()
作用:设置线程中断标志/唤醒阻塞中的线程
Thread worker = new Thread(() -> {while (!Thread.interrupted()) {try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("接收中断信号");break;}}
});
worker.start();
worker.interrupt(); // 发送中断信号
阻塞状态响应:
-
sleep()/wait()/join()会立即抛出InterruptedException
-
抛出异常后自动清除中断标志
try {Thread.sleep(1000);
} catch (InterruptedException e) {// 此处中断标志已被清除Thread.currentThread().interrupt(); // 恢复中断状态
}
中断标志检查:
// 方法1:检查并清除标志(静态方法)
if (Thread.interrupted()) {// 处理中断逻辑
}// 方法2:检查标志不改变状态(实例方法)
if (thread.isInterrupted()) {// 处理中断逻辑
}
线程等待:join()
作用:使当前线程等待目标线程终止
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);t1.start();
t1.join(500); // 最多等待500ms
System.out.println("t1状态: " + t1.getState());t2.start();
t2.join(); // 无限等待直至完成
方法重载:
方法签名 | 作用描述 |
---|---|
void join() | 无限等待直至线程终止 |
void join(long millis) | 最多等待指定毫秒数 |
void join(long millis, int nanos) | 高精度等待(实际精度依赖系统实现) |
注意事项:
- 调用join()的线程会进入WAITING/TIMED_WAITING状态
- 可被interrupt()中断等待
- 不要在主线程中join自身(导致死锁)
线程休眠:sleep()
作用:使当前线程暂停执行指定时间
try {Thread.sleep(2000); // 休眠2秒Thread.sleep(1500, 500000); // 1.5秒+500000纳秒
} catch (InterruptedException e) {// 中断处理逻辑
}
核心特性:
特性 | 说明 |
---|---|
锁保持 | 休眠期间保持持有的所有锁 |
时间精度 | 实际休眠时间>=参数值(受系统计时器和调度器影响) |
中断响应 | 休眠期间收到中断会抛出InterruptedException |
状态转换 | 进入TIMED_WAITING状态 |
典型应用场景:
-
模拟耗时操作
-
控制任务执行频率
-
防止CPU空转消耗资源
注意要点:
// 错误用法:用sleep实现同步控制
while (!condition) {Thread.sleep(100); // 应使用wait/notify机制
}// 正确用法:定时任务间隔
void run() {while (running) {processTask();Thread.sleep(1000); // 每秒执行一次}
}
四.线程的状态
状态 | 描述 |
---|---|
NEW | 创建未启动 |
RUNNABLE | 可运行(包括就绪和运行中) |
BLOCKED | 等待监视器锁 |
WAITING | 无限期等待其他线程通知 |
TIMED_WAITING | 带超时的等待 |
TERMINATED | 执行完成 |
NEW(新建状态)
特征:
- 线程对象已创建
- 未调用start()方法
- 未分配系统资源
触发条件:
Thread t = new Thread(() -> {});
System.out.println(t.getState()); // 输出NEW
RUNNABLE(可运行状态)
子状态:
- Ready:等待CPU时间片
- Running:正在执行
触发条件:
t.start(); // 从NEW进入RUNNABLE
synchronized(lock) { // 锁释放后从BLOCKED回到RUNNABLE
}
BLOCKED(阻塞状态)
产生场景:
- 等待进入synchronized代码块
- 等待进入synchronized方法
触发条件:
Object lock = new Object();// 线程1
new Thread(() -> {synchronized(lock) {while(true); // 长期持有锁}
}).start();// 线程2
Thread t2 = new Thread(() -> {synchronized(lock) { // 此处阻塞System.out.println("获得锁");}
});
t2.start();
System.out.println(t2.getState()); // 输出BLOCKED
WAITING(无限等待)
触发条件:
object.wait(); // 未设置超时
thread.join(); // 未设置超时
LockSupport.park();
状态特征:
- 需要其他线程主动唤醒
- 不占用CPU资源
- 常见于线程协调场景
TIMED_WAITING(限期等待)
典型方法:
Thread.sleep(1000);
object.wait(500);
thread.join(3000);
LockSupport.parkNanos(1000000);
代码示例:
Thread t = new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}
});
t.start();
System.out.println(t.getState()); // 输出TIMED_WAITING
终止条件:
- run()方法正常执行结束
- 未捕获异常导致线程终止
触发条件:
Thread t = new Thread(() -> {});
t.start();
t.join();
System.out.println(t.getState()); // 输出TERMINATED
五.线程安全(重点)
什么是线程安全?
核心定义:当多个线程并发访问共享资源时,程序仍能保持正确行为的状态
黄金准则:当且仅当满足以下条件时,程序才是线程安全的:
- 原子性(Atomicity)保证
- 可见性(Visibility)保证
- 有序性(Ordering)保证
原子性(Atomicity)
原子性要求对共享资源的操作是不可分割的完整单元,其他线程只能看到操作前或操作后的状态,不能观察到中间状态。
// 非原子操作示例
public class UnsafeCounter {private int count = 0;public void increment() {count++; // 实际包含三个步骤:读值、修改、写回}
}
//mov eax, [count] ; 读取当前值到寄存器
//inc eax ; 寄存器值加1
//mov [count], eax ; 将新值写回内存
当两个线程交错执行时:
线程A:读取count=5 → 暂停
线程B:完成完整操作count=6
线程A:继续执行,最终count=6(正确值应为7)
可见性(Visibility)
可见性保证一个线程修改共享变量后,其他线程能立即感知到最新值。
可见性问题根源(Java内存模型)
-
工作内存机制:每个线程有独立的工作内存(CPU缓存),优先读取本地缓存而非主内存
-
缺乏同步机制:普通变量修改后不强制刷新到主内存,其他线程可能读取到过期值
-
JIT编译器优化:热点代码可能被优化为直接使用寄存器中的值,跳过内存读取
public class VisibilityDemo {// 不加volatile会导致死循环private static boolean flag = true; // 无volatile修饰public static void main(String[] args) throws InterruptedException {new Thread(() -> {while (flag) { // 空循环(无同步操作)}System.out.println("线程退出");}).start();Thread.sleep(1000);flag = false; // 主线程修改标志位}
}
导致死循环的详细原因
1 工作内存缓存
子线程启动时:
-
从主内存读取
flag=true
到工作内存 -
后续循环直接使用工作内存中的副本值
主线程修改flag
后:
-
修改的是主内存中的值(但不确定何时刷新)
-
子线程的工作内存副本未失效,仍认为
flag=true
2 JIT编译器优化
-
当循环检测到
flag
未被修改时,可能进行激进优化:
; 伪汇编代码(优化后)
LOOP:cmp [flag], true // 被优化删除jmp LOOP // 直接无限循环
- 优化依据:循环体内无任何同步操作,假设
flag
不会改变
3 内存屏障缺失
普通变量的读写不插入内存屏障:
-
主线程修改
flag
后不强制刷新到主内存 -
子线程不强制从主内存重新加载值
有序性(Ordering)
有序性保证程序执行顺序符合代码的先后关系,防止编译器和处理器优化导致的指令重排序。
// 危险的双重检查锁
public class Singleton {private static Singleton instance;public static Singleton getInstance() {if (instance == null) { // 第一次检查synchronized (Singleton.class) {if (instance == null) { // 第二次检查instance = new Singleton(); // 问题根源}}}return instance;}
}
对象初始化过程可能被重排序为:
- 分配内存空间
- 将引用指向内存空间(此时instance != null)
- 初始化对象
导致其他线程可能获得未初始化的实例。
解决之前的线程不安全问题
synchronized 关键字
核心作用
-
原子性:确保代码块内的操作不可分割
-
可见性:修改后的变量值对其他线程立即可见
-
有序性:防止代码块内的指令重排序
使用场景
-
需要保证复合操作的原子性(如:计数器累加)
-
多线程共享资源的互斥访问(如:数据库连接池)
-
需要实现线程间协作(配合
wait()
/notify()
)
使用方法
必须显式指定锁对象,推荐使用私有final对象,避免使用this
或公共对象
实例锁(对象级别)
public class OrderService {// 1. 创建私有final锁对象(强制不可修改)private final Object orderLock = new Object();private int stock = 100;public void placeOrder() {// 2. 使用指定锁对象同步synchronized(orderLock) { if(stock > 0) {stock--;System.out.println("下单成功,剩余库存:" + stock);}}}
}
类锁(全局级别)
public class GlobalCounter {// 1. 创建类级别锁对象(static final)private static final Object CLASS_LOCK = new Object();private static int count = 0;public static void increment() {// 2. 使用类锁对象同步synchronized(CLASS_LOCK) {count++;}}
}
volatile 关键字
核心作用
-
可见性:变量修改后立即对所有线程可见
-
有序性:禁止指令重排序优化
使用场景
-
状态标志位(如:线程终止标志)
-
单次原子操作(如:long/double类型变量)
-
双重检查锁定模式(DCL单例)
使用方法
状态标志控制
public class WorkerThread implements Runnable {private volatile boolean running = true;public void run() {while(running) {// 执行任务...}}public void stop() {running = false; // 其他线程修改后立即生效}
}
单例模式实现
public class Singleton {private static volatile Singleton instance;public static Singleton getInstance() {if(instance == null) {synchronized(Singleton.class) {if(instance == null) {instance = new Singleton(); // volatile防止指令重排序}}}return instance;}
}
独立观察变量
public class SensorMonitor {private volatile double temperature;// 多个线程读取温度值public double getCurrentTemp() {return temperature; // 总是获取最新值}// 专用线程更新温度值public void updateTemp(double newValue) {temperature = newValue;}
}
原子性问题解决方案
synchronized同步锁
public class SafeCounter {private int count = 0;public synchronized void increment() {count++; // 保证原子操作的黄金方案}
}
实现原理:
- Monitor锁机制确保同一时刻只有一个线程能访问临界区
- 内存屏障强制工作内存与主内存同步
- 可重入设计避免死锁
适用场景:
- 复杂的复合操作
- 需要保证操作完整性的关键业务
可见性问题解决方案
volatile关键字
public class VisibilitySolution {private volatile boolean flag = true;public void start() {new Thread(() -> {while(flag) { /* 即时可见 */ }}).start();}
}
内存屏障机制:
- 写操作前插入StoreStore屏障
- 写操作后插入StoreLoad屏障
- 读操作前插入LoadLoad屏障
适用限制:
- 仅适用于单个变量的状态标记
- 不保证复合操作的原子性
有序性问题解决方案
双重检查锁优化
public class Singleton {private static volatile Singleton instance;public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton(); }}}return instance;}
}
volatile的魔法作用:
- 禁止new操作的指令重排序
- 保证对象初始化完成后才赋值引用
- 内存屏障阻止其他线程访问未初始化对象
六.wait()与notify()
核心作用解析
1. 线程协调的基石
wait()
和notify()
是Java线程间通信的基础方法,用于解决以下典型场景:
- 生产者消费者模式(缓冲队列控制)
- 任务协调(主从线程协作)
- 资源调度(线程按条件执行)
2. 方法定位
方法 | 作用描述 |
---|---|
wait() | 释放锁并暂停线程,直到其他线程通知 |
notify() | 随机唤醒一个等待线程 |
notifyAll() | 唤醒所有等待线程 |
标准使用模板
1. 基础协作模式
public class TaskCoordinator {private final Object lock = new Object();private boolean isReady = false;// 等待侧代码public void waitForCondition() throws InterruptedException {synchronized(lock) {while(!isReady) { // 必须使用循环检查lock.wait(); // 释放锁并等待}// 条件满足后执行任务System.out.println("执行核心业务...");}}// 通知侧代码public void notifyCondition() {synchronized(lock) {isReady = true; // 修改条件状态lock.notifyAll(); // 推荐使用notifyAll}}
}
2. 生产者-消费者实现
public class MessageBuffer {private final LinkedList<String> queue = new LinkedList<>();private final int maxSize;private final Object lock = new Object();public MessageBuffer(int maxSize) {this.maxSize = maxSize;}// 生产者方法public void produce(String message) throws InterruptedException {synchronized(lock) {while(queue.size() == maxSize) { // 缓冲区满时等待lock.wait();}queue.add(message);lock.notifyAll(); // 唤醒所有消费者}}// 消费者方法public String consume() throws InterruptedException {synchronized(lock) {while(queue.isEmpty()) { // 缓冲区空时等待lock.wait();}String message = queue.removeFirst();lock.notifyAll(); // 唤醒所有生产者return message;}}
}
关键使用原则
1. 必须遵守的黄金法则
- 同步块内使用:必须在
synchronized
代码块中调用 - 循环检查条件:防止虚假唤醒(spurious wakeup)
- 私有锁对象:推荐使用专用Object实例作为锁
// 正确示例:私有锁对象
private final Object lock = new Object();// 错误示例:使用公共对象锁
public Object publicLock = new Object();
2. 选择通知策略
场景 | 策略选择 | 说明 |
---|---|---|
单一等待条件 | notify() | 随机唤醒一个线程 |
多个等待条件 | notifyAll() | 避免线程饿死 |
复杂条件判断 | notifyAll() | 确保所有等待线程重新检查条件 |
3. 超时控制(避免永久阻塞)
public void timedWait() throws InterruptedException {synchronized(lock) {long timeout = 5000; // 5秒超时long remaining = timeout;long startTime = System.currentTimeMillis();while(!condition && remaining > 0) {lock.wait(remaining);remaining = timeout - (System.currentTimeMillis() - startTime);}if(condition) {// 执行后续操作}}
}
七.多线程案例
单例模式
单例模式是校招中最常考的设计模式之一.
啥是设计模式? 设计模式好比象棋中的 "棋谱". 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有 一些固定的套路. 按照套路来走局势就不会吃亏. 软件开发中也有很多常见的 "问题场景". 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照 这个套路来实现代码, 也不会吃亏. 单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例. 这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.
单例模式具体的实现方式, 分成 "饿汉" 和 "懒汉" 两种
饿汉模式
类加载的同时, 创建实例.
class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
懒汉模式-单线程版
类加载的时候不创建实例. 第一次使用的时候才创建实例
class Singleton {private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
懒汉模式-多线程版
上面的懒汉模式的实现是线程不安全的.
线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致 创建出多个实例. 一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改 instance 了)
加上 synchronized 可以改善这里的线程安全问题
class Singleton {private static Singleton instance = null;private Singleton() {}public synchronized static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
懒汉模式-多线程版(改进)
以下代码在加锁的基础上, 做出了进一步改动:
- 使用双重 if 判定, 降低锁竞争的频率.
- 给 instance 加上了 volatile.
class Singleton {private static volatile Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
阻塞式队列
阻塞队列是什么
阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则.
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:
- 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
- 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型.
基础版阻塞队列
/*** 线程安全的阻塞队列实现* @param <T> 队列元素类型*/
public class SimpleBlockingQueue<T> {// 存储队列元素的数组(循环数组)private final Object[] items;// 当前队列中的元素数量private int count;// 下一个插入位置的索引private int putIndex;// 下一个取出位置的索引private int takeIndex;// 同步锁对象(推荐使用专用锁对象)private final Object lock = new Object();/*** 初始化指定容量的队列* @param capacity 队列容量(必须>0)*/public SimpleBlockingQueue(int capacity) {this.items = new Object[capacity];}/*** 将元素放入队列(队列满时阻塞等待)* @param item 要添加的元素(不能为null)* @throws InterruptedException 等待时被中断抛出*/public void put(T item) throws InterruptedException {synchronized (lock) {// 循环检查队列是否已满(防止虚假唤醒)while (count == items.length) {// 释放锁并进入等待状态lock.wait();}// 插入元素到当前位置items[putIndex] = item;// 更新插入位置(循环处理)if (++putIndex == items.length) putIndex = 0;// 元素计数增加count++;// 唤醒所有等待线程(可能有消费者在等待)lock.notifyAll();}}/*** 从队列取出元素(队列空时阻塞等待)* @return 取出的元素* @throws InterruptedException 等待时被中断抛出*/public T take() throws InterruptedException {synchronized (lock) {// 循环检查队列是否为空(防止虚假唤醒)while (count == 0) {// 释放锁并进入等待状态lock.wait();}// 获取当前取出位置的元素T item = (T) items[takeIndex];// 更新取出位置(循环处理)if (++takeIndex == items.length) takeIndex = 0;// 元素计数减少count--;// 唤醒所有等待线程(可能有生产者在等待)lock.notifyAll();return item;}}
}
定时器
定时器是什么
定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定 好的代码.
时间轮定时器
public class SimpleTimer {private final PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>(Comparator.comparingLong(t -> t.execTime));private final Thread worker;public SimpleTimer() {worker = new Thread(() -> {while (true) {try {Task task = queue.take();long curr = System.currentTimeMillis();if (task.execTime > curr) {Thread.sleep(task.execTime - curr);}task.runnable.run();} catch (InterruptedException e) {break;}}});worker.start();}public void schedule(Runnable task, long delayMs) {queue.put(new Task(task, System.currentTimeMillis() + delayMs));}private static class Task {final Runnable runnable;final long execTime;Task(Runnable runnable, long execTime) {this.runnable = runnable;this.execTime = execTime;}}
}
使用方法:
SimpleTimer timer = new SimpleTimer();
timer.schedule(() -> System.out.println("5秒后执行"), 5000);
线程池
线程池是什么
虽然创建线程 / 销毁线程 的开销
想象这么一个场景:在学校附近新开了一家快递店,老板很精明,想到一个与众不同的办法来经营——店里不雇佣固定员工,而是每次有业务时,临时找一名同学来送快递,送完立即解雇。这就像我们处理任务时“来一个任务,起一个线程”的模式。
然而,老板很快发现问题:每次“招聘+解雇”同学的成本极高。善于变通的老板恍然大悟,明白了为何其他公司都选择长期雇人。于是他制定新规则:将公司业务人员逐步扩张到3人,并根据业务需求灵活调整。
具体运作流程如下:当新业务到来时,老板先检查现有员工数量。若未满3人,则立即雇佣一人送快递;若已达3人,则将业务记录在任务本上,等待现有快递员空闲时处理。这正是线程池的核心模式。
线程池最大的好处就是减少每次启动、销毁线程的损耗
核心线程池实现
public class SimpleThreadPool {private final BlockingQueue<Runnable> taskQueue;private final List<Worker> workers = new ArrayList<>();public SimpleThreadPool(int coreSize) {taskQueue = new LinkedBlockingQueue<>();for (int i = 0; i < coreSize; i++) {Worker worker = new Worker();workers.add(worker);worker.start();}}public void execute(Runnable task) {taskQueue.offer(task);}private class Worker extends Thread {public void run() {while (!isInterrupted()) {try {Runnable task = taskQueue.take();task.run();} catch (InterruptedException e) {break;}}}}
}
使用方法:
SimpleThreadPool pool = new SimpleThreadPool(4);
pool.execute(() -> System.out.println("执行任务"));
总结-保证线程安全的思路
使用没有共享资源的模型
适用共享资源只读,不写的模型
- 不需要写共享资源的模型
- 使用不可变对象
直面线程安全(重点)
- 保证原子性
- 保证顺序性
- 保证可见性
相关文章:
Java多线程编程初阶指南
目录 一.线程基础概念 线程是什么? 线程与进程对比 为啥要有线程 二.线程实现方式 继承Thread类 实现Runnable接口 常规实现方式 匿名内部类写法 Lambda表达式写法(Java8) 对比总结 三.Thread 类及常见方法 核心功能 核心构造方…...
Qt信号槽连接的三种方法对比
信号槽连接方法对比 1. 直接连接2. 集中管理3.函数指针初始化列表后期需要disconnect 对比 1. 直接连接 connect(codeWindow, &CodeEditorWindow::SetBaseLineSignal, monitoringWindow, &MonitoringWindow::SetBaseLineSlot),connect(&ButtonTree::Instance(), &a…...
健康生活新指南
在 “朋克养生” 与 “躺平焦虑” 并存的时代,真正的健康生活无需刻意 “内卷”。这几个简单又实用的养生妙招,能让你在忙碌日常中悄悄升级健康状态,轻松拥抱活力人生。 一、饮食:吃对食物,给身体 “加 Buff” 别…...
IF=24.5 靶向MMP9治疗协同提高抗PD1疗效
Targeted MMP9 therapy synergistically improves anti-PD1 efficacy CTNNB1GOF(The gain of function (GOF) CTNNB1 mutations,功能获得型CTNNB1突变)在肝细胞癌(HCC)中,已被证明与免疫排斥相关࿰…...
基于SpringBoot的中华诗词文化分享平台-项目分享
基于SpringBoot的中华诗词文化分享平台-项目分享 项目介绍项目摘要管理员功能图会员功能图系统功能图项目预览会员主页面诗词页面发布问题回复评论 最后 项目介绍 使用者:管理员、会员 开发技术:MySQLJavaSpringBootVue 项目摘要 本文旨在设计与实现一…...
SQLiteDatabase 增删改查(CRUD)详细操作
文章目录 1. 初始化数据库2. 插入数据 (Create)方法一:使用 ContentValues insert()方法二:直接执行SQL 3. 查询数据 (Read)方法一:使用 query() 方法方法二:使用 rawQuery() 执行原始SQL 4. 更新数据 (Update)方法一:…...
从 0 到 1 打通 AI 工作流:Dify+Zapier 实现工具自动化调用实战
一、引言:当 AI 遇到工具孤岛 在企业数字化转型的浪潮中,AI 工具的应用早已从单一的对话交互进阶到复杂的业务流程自动化。但开发者常常面临这样的困境:本地开发的 MCP 工具(如 ERP 数据清洗脚本、CRM 工单系统 API)如…...
第四届商师校赛 web 1
RceMe ezGame 伪装 Ping Are you from SQNU? Look for the homepage Through 根据题目慢慢试 File_download Post上传得到下载文件 反编译一下 /* * Decompiled with CFR 0.152. * * Could not load the following classes: * javax.servlet.http.HttpServlet */ …...
SSH 互信被破坏能导致 RAC 异常关闭吗
一、 SSH 互信和 RAC 的关系 1、SSH 互信对 RAC 的作用 Oracle 11g R2 在安装 Grid Infrastructure 的时候,能够通过安装程序配置节 点间的 SSH 用户等效性,之所以要在安装之前配置 SSH 用户等效性,是为了能 够在安装前使用 C…...
工程投标k值分析系统(需求和功能说明)
1 需求总括 2 企业管理模块: 新增、删除、修改企业/部门 <...
Qt-托盘的实现
文章目录 托盘的功能QSystemTrayIcon 类QSystemTrayIcon类的常用函数代码实现 托盘的功能 GUI 程序,如果想要实现当最小化时,程序从任务栏消失,在系统托盘显示一个图标,表示此程序,并能在托盘内通过双击或者菜单使程序…...
【人脸识别】百度人脸识别H5方案对接
经调研,百度的人脸识别使用场景比较广泛且准确率较高,项目上有用到,这里做一下记录,整体对接没有难度,按照文档操作就行。 一、准备工作 1、需要注册百度云开放平台(企业资质)注册指南 2、创…...
用Qt和deepseek创建自己的问答系统
如果你不想花钱调用deepseek,试试下面的方法。 1: 访问 OpenRouter: https://openrouter.ai 2: 搜索 DeepSeek-R1 (free) 要使用这个免费模型,你需要: (1)注册 OpenRouter 账户并获取 API 密钥 访问 …...
飞搭系列 | 组件增加标记,提升用户体验
前言 Preface 飞搭低代码平台(FeiDa,以下简称“飞搭”),为企业提供在线化、灵活的业务应用构建工具,支持高低代码融合,助力企业低门槛、高效率和低成本地快速应对市场变化,加速复杂业务场景落地…...
布隆过滤器的应用
布隆过滤器虽然看起来是一个“算法结构”,但在实际 Web 应用场景中用途非常广泛,尤其在 提升性能、节省资源、防御攻击 等方面非常有用。 缓存穿透保护(常见于 Redis) 📌 问题: 用户频繁请求一些数据库中…...
云原生--基础篇-4--CNCF-1-云原生计算基金会(云原生生态发展和目标)
1、CNCF定义与背景 云原生计算基金会(Cloud Native Computing Foundation,CNCF)是由Linux基金会于2015年12月发起成立的非营利组织,旨在推动云原生技术的标准化、开源生态建设和行业协作。其核心目标是通过开源项目和社区协作&am…...
(16)VTK C++开发示例 --- 转换文件格式
文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容👉内容导航 👈👉VTK开发 👈 1. 概述 此示例演示如何读取文件,然后将其写入不同类型的文件。 在此示例中,我们读取一个 vtp 文件并…...
离线-DataX
基本介绍 DataX 是阿里云 DataWorks数据集成的开源版本,在阿里巴巴集团内被广泛使用的离线数据同步工具/平台,它是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源…...
深度学习-全连接神经网络-3
七、过拟合与欠拟合 在训练深层神经网络时,由于模型参数较多,在数据量不足时很容易过拟合。而正则化技术主要就是用于防止过拟合,提升模型的泛化能力(对新数据表现良好)和鲁棒性(对异常数据表现良好)。 1. 概念认知 …...
基于javaweb的SSM+Maven教材管理系统设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…...
DCL介绍
一.dcl-介绍 一.案例 1.查询用户 USE mysql; select * from user; 2.权限控制...
mysql日常巡检
1.查看mysql服务是否异常 systemctl status mysql_3306 查看MySQL进程是否存在 ps -ef | grep mysql 2.连接异常检查 (1)查看是否异常连接 show processlist; #或 show full processlist; (2)查看当前失败连接数 show global status like aborted_connects; (3)查看试…...
Cursor这类编程Agent软件的模型架构与工作流程
开发|界面|引擎|交付|副驾——重写全栈法则:AI 原生的倍速造应用流 来自全栈程序员 nine 的探索与实践,持续迭代中。 欢迎评论私信交流。 最近在关注和输出一系列 AIGC 架构。 模型架构与工作流程 大语…...
记录:扩展欧几里得算法
本文遵循 CC BY-NC-ND 4.0 协议,作者: U•ェ•*U \texttt{U•ェ•*U} U•ェ•*U,转载请获得作者授权。 前置知识 裴蜀定理/贝祖定理:若 a , b a,b a,b 是整数,且 gcd ( a , b ) d \gcd(a,b)d gcd(a,b)d…...
学习笔记——《Java面向对象程序设计》-抽象和接口
参考教材: Java面向对象程序设计(第3版)微课视频版 清华大学出版社 抽象方法 抽象方法是使用abstract关键字修饰的成员方法,抽象方法在定义时不需要实现方法体。 抽象方法的定义格式如下: abstract void 方法名称…...
MySQL中根据binlog日志进行恢复
MySQL中根据binlog日志进行恢复 排查 MySQL 的 binlog 日志问题及根据 binlog 日志进行恢复的方法一、引言二、排查 MySQL 的 binlog 日志问题(一)确认 binlog 是否开启(二)查找 binlog 文件位置和文件名模式(三&#…...
数据库sql语句 中 GROUP BY 关键字详解及字段要求
GROUP BY 关键字详解及字段要求 GROUP BY 的核心作用 将查询结果按指定字段分组,常与聚合函数(如 COUNT, SUM, AVG 等)结合使用,对分组后的数据进行统计计算。 GROUP BY 后字段的要求 非聚合字段必须出现在 GROUP BY 子句中&…...
数据集 | 柑橘果目标检测数据集
文章目录 一、数据集概述1.1 数据标注实例1.2 数据集技术规格 二、样本类别详解2.1 树上柑橘样本2.2 树下柑橘样本 三、标注工具四、数据下载地址 一、数据集概述 在农业智能化领域,柑橘果园的自动化监测与管理一直面临着几个关键挑战: 果实定位准确性…...
Arduino示例代码讲解:Project 11 - Crystal Ball 水晶球
Arduino示例代码讲解:Project 11 - Crystal Ball 水晶球 Project 11 - Crystal Ball 水晶球程序功能概述功能:硬件要求:输出:代码结构全局变量`setup()` 函数`loop()` 函数读取倾斜开关状态:检测状态变化:保存状态:运行过程注意事项Project 11 - Crystal Ball 水晶球 /…...
Redis—为何持久化使用子进程
AOF重写以及bgsave的时候为什么采用fork子进程而不是子线程? 进程间内存隔离 独立的内存空间:子进程拥有与主进程独立的内存空间,确保即使在重写过程中发生崩溃或错误,也不会影响主进程的运行和内存状态。 数据安全性ÿ…...
Vue3 + Vite + TS,使用 ExcelJS导出excel文档,生成水印,添加背景水印,dom转图片,插入图片,全部代码
Vue3 Vite TS,使用 ExcelJS导出excel文档,生成水印,添加背景水印,dom转图片,插入图片,全部代码 ExcelJS生成文档并导出导出表头其他函数 生成水印设置文档的背景水印dom 转图片插入图片全部代码 ExcelJS 读取&#…...
VulnHub-DarkHole_1靶机渗透教程
VulnHub-DarkHole_1靶机渗透教程 1.靶机部署 [Onepanda] Mik1ysomething 靶机下载:https://download.vulnhub.com/darkhole/DarkHole.zip 直接使用VMware打开就行 导入成功,打开虚拟机,到此虚拟机部署完成! 注意:…...
Python设计模式:对象池
1. 什么是对象池设计模式? 对象池设计模式是一种创建型设计模式,主要用于管理和复用对象,以提高性能和资源利用率。它通过维护一个对象的集合(池),来避免频繁地创建和销毁对象,从而减少内存分配…...
【上海大学数据库原理实验报告】MySQL数据库的C/S模式部署
实验目的 掌握Linux环境下MySQL数据库的安装、初始化和基本配置。通过配置MySQL的网络通信,熟悉数据库的远程访问机制及其安全性要求。 实验内容 在腾讯云上租借两台服务器,打开3306端口以允许MySQL远程访问。 图 1 租到的服务器可在控制台观察其状态…...
deepseek快速生成简历
目录 一、需求二、模板例子 三、生成简历四、其他说明 一、需求 现在我准备跳槽到一家公司,这家公司已经发布了招聘需求,现在你想跳槽到这家公司,我们可以利用deepseek快速生成符合这家公司的简历内容。 二、模板 我们要进行指令明确的结构…...
什么是机器视觉3D无序堆叠抓取
机器视觉3D无序堆叠抓取是一种结合三维视觉感知、人工智能算法和机器人控制的技术,旨在从杂乱无序堆叠的物体中识别、定位并抓取目标物体。该技术广泛应用于工业自动化(如物流分拣、装配制造)、仓储管理、食品加工等领域,解决了传统二维视觉或固定规则堆叠场景下的抓取难题…...
Shell脚本中的字符串截取和规则变化
文章目录 前言if通配符判断if判断多个条件规则变化字符串的两个示例改变中间段数字改变末尾段数字 总结 前言 科技的发展会带来习惯的改变,特别是对于我们这批敲代码的,之前还积累一些奇巧淫技,想着在必要的时候卖弄一下,自从生成…...
云账号安全事件应急响应指南:应对来自中国IP的异常访问
在当今数字化时代,云服务已成为企业IT基础设施的核心。然而,随之而来的安全挑战也日益突出。本文将详细介绍当发现云账号被来自中国的IP地址异常利用时,应如何快速有效地响应,以确保账户安全并最小化潜在风险。 1. 确认异常活动 首先,我们需要确认是否真的发生了安全事件…...
python番外
#作者:允砸儿 #日期:乙巳青蛇年 三月廿五 在开始数据库的分享之前笔者简单写以下关于python的番外。笔者这块可能写的不是很好csdn上面有很多大佬,笔者仅以自己的思维和想法与大家分享以下。 安装必看 笔者在这里贴一个网址https://www.…...
Jetson Orin NX 16G 配置GO1强化学习运行环境
这一次收到了Jrtson Orin NX, 可以进行部署了。上一次在nano上的失败经验 Jetson nano配置Docker和torch运行环境_jetson docker-CSDN博客 本次的目的是配置cuda-torch-python38环境离机运行策略。 Jetson Orin NX SUPER 1. 烧录镜像 参考链接在ubuntu系统中安装sdk manag…...
Embedding与向量数据库__0422
thinking:做一个项目 1,业务背景,价值 2,方法,工具 3,实践(现有的代码,改写的代码) cursor编程有个cursor settings ->privacy mode隐私模式,但是只要连上…...
正向代理和反向代理
正向代理和反向代理是两种在不同场景下使用的代理技术,它们有以下区别: 目标和作用 正向代理 目标 :主要是为客户端服务,帮助客户端去访问外部网络资源。例如,企业内部网络中的员工可能需要访问互联网,但直…...
Android JNI开发中头文件引入的常见问题与解决方案,提示:file not found
Android JNI开发中头文件引入的常见问题与解决方案 问题场景(新手易犯错误) 假设你在开发一个JNI项目,想要实现一个线程安全的队列(SafeQueue),于是直接在cpp目录下创建了safe_queue.h文件,并开…...
三网通电玩城平台系统结构与源码工程详解(二):Node.js 服务端核心逻辑实现
本篇文章将聚焦服务端游戏逻辑实现,以 Node.js Socket.io 作为主要通信与逻辑处理框架,展开用户登录验证、房间分配、子游戏调度与事件广播机制的剖析,并附上多个核心代码段。 一、服务端文件结构概览 /server/├── index.js …...
案例:Windows 作为客户端免密验证(公钥验证)
一、实验前提 1.服务器端为 Linux 系统,且能够正常运行相关命令和服务,如 systemctl、ssh 服务等。同时,客户端为 Windows 系统,且具备使用 SSH 客户端工具连接到 Linux 服务器的条件 2.Linux 服务器上已安装并配置好 SSH 服务&…...
leetcode 二分查找
704. Binary Search 代码: class Solution { public:int search(vector<int>& nums, int target) {int n nums.size();int left 0;int right n-1;int res -1;while(left < right){int mid (leftright)/2;if(nums[mid] target){res mid;break;}…...
C++:STL模板
STL模板分为函数模板和类模板。 我想交换两个数字,但是类型不同,例如我想交换整形a,b,和double类型的d1,d2。如果使用C语言来实现,那么需要像下面一样写两个swap函数,但是除了类型不同,其它都一样…...
NumPy入门:从数组基础到数学运算
目录 一、NumPy 数组基础 (一)创建数组 (二)数组索引 (三)数组切片 二、数组操作 (一)形状操作 (二)数组合并与分割 三、基本数学运算 (…...
文档管理 Document Management
以下是关于项目管理中 文档管理 的深度解析,结合高项(如软考高级信息系统项目管理师)教材内容,系统阐述文档管理的理论框架、核心流程及实战应用: 一、文档管理的基本概念 1. 定义 文档管理是对项目全生命周期中产生的各类文档进行规范化管理的过程,包括创建、存储、版…...
TCP三次握手与四次挥手面试回答版本
面试官:说一下TCP三次握手的过程 参考面试回答: 在第一次握手的时候、客户端会随机生成初始化序号、放到TCP报文头部的序号字段中、同时把SYN标志设置为1 这样就表示SYN报文(这里是请求报文)。客户端将报文放入 TCP 报文首部的序…...