一,理解线程和进程
1.进程
后台运行的的软件,程序
2. 什么是线程?
共享资源:同一进程下的线程共享代码段、全局变量和堆内存,这使得线程间通信非常高效。
独立实体:尽管共享资源,但每个线程拥有独立的程序计数器 (PC)、寄存器集合和栈 (Stack),确保其执行流的独立性。
调度单位:操作系统通过调度算法(如时间片轮转)让线程在 CPU 上轮流运行,营造"同时进行"的假象(并发)。
线程是进程的最小执行单元,一个进程可以包含多个线程(单线程进程只有 1 个线程,多线程进程有多个)。
二,创建线程
1,继承 Thread 类
python
class MyThread extends Thread {
public void run() {
System.out.println("方式1:继承 Thread 运行");
}
}
// 启动: new MyThread().start();
缺点:这是最简单的写法,但因为 Java 是单继承机制,继承了 Thread 就无法继承其他类,灵活性差
2,和实现 Runnable接口
java
class MyTask implements Runnable {
public void run() {
System.out.println("方式2:实现 Runnable 运行");
}
}
// 启动: new Thread(new MyTask()).start();
缺点:Runnable 的痛点是不能返回值,也不能抛出受检异常。
3,实现 Callable 接口
python
class Caller implements Callable<String> {
public String call() throws Exception {
return "方式3:带返回值的线程执行结果";
}
}
// 启动需配合 FutureTask
FutureTask<String> task = new FutureTask<>(new Caller());
new Thread(task).start();
// String result = task.get(); // 阻塞等待获取结果
缺点:Runnable 的痛点是不能返回值,也不能抛出受检异常。Callable 配合 FutureTask 解决了这个问题。
4,线程池
python
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 自定义 ThreadFactory (为了给线程命名)
ThreadFactory namedThreadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "My-Biz-Thread-" + threadNumber.getAndIncrement());
System.out.println(">>> 创建了一个新线程: " + t.getName());
return t;
}
};
// 2. 创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // corePoolSize: 核心线程数
4, // maximumPoolSize: 最大线程数
60L, TimeUnit.SECONDS, // keepAliveTime: 空闲存活时间
new ArrayBlockingQueue<>(2), // workQueue: 有界队列 (容量2)
namedThreadFactory, // threadFactory: 线程工厂
new ThreadPoolExecutor.AbortPolicy() // handler: 拒绝策略
);
// 3. 模拟提交任务,观察创建过程
// 任务 1, 2 -> 创建核心线程 (当前线程数: 0->2)
// 任务 3, 4 -> 进入队列 (队列容量: 0->2)
// 任务 5, 6 -> 队列满,创建非核心线程 (当前线程数: 2->4)
// 任务 7 -> 爆满,触发拒绝策略
for (int i = 1; i <= 7; i++) {
final int taskId = i;
try {
pool.execute(() -> {
try { Thread.sleep(2000); } catch (InterruptedException e) {}
System.out.println("线程 [" + Thread.currentThread().getName() + "] 完成任务 " + taskId);
});
} catch (RejectedExecutionException e) {
System.err.println("任务 " + taskId + " 被拒绝了");
}
}
pool.shutdown();
}
}
三,线程池
1,线程池的创建
java
public ThreadPoolExecutor(
int corePoolSize, // 1. 核心线程数
int maximumPoolSize, // 2. 最大线程数
long keepAliveTime, // 3. 空闲存活时间
TimeUnit unit, // 4. 时间单位
BlockingQueue<Runnable> workQueue, // 5. 任务队列
ThreadFactory threadFactory, // 6. 线程工厂
RejectedExecutionHandler handler // 7. 拒绝策略
)
2,线程池的参数
-
corePoolSize(核心柜员): 银行常驻的柜员窗口。即使没有客户,他们也在那坐着。 -
workQueue(候客区): 核心柜员忙不过来时,客户排队的区域(阻塞队列)。 -
maximumPoolSize(最大柜员): 银行由于客流太大,临时加开的窗口 + 核心窗口的总数。 -
keepAliveTime(临时工下班时间): 临时加开的窗口空闲了多久后会被关闭。 -
threadFactory(工牌制作): 给线程起名字、设置优先级的地方。 -
handler(保安): 窗口满了、排队区也满了,保安出来告诉新来的客户:"今天不办了,请回吧"。 -
threadFactory:线程工程
java
ThreadFactory namedThreadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return Thread ;
}
3,拒绝策略
| 策略类名 | 行为描述 | 适用场景 | 风险 |
|---|---|---|---|
| AbortPolicy (默认) | 直接抛异常 (RejectedExecutionException),阻止系统正常运行。 |
默认策略,适合需要感知任务失败的场景。 | 如果不捕获异常,会导致调用者程序中断。 |
| CallerRunsPolicy | "谁调用的谁去跑" 。不抛弃任务,也不抛异常,而是由调用者线程(比如主线程)自己去执行这个任务。 | 生产环境常用 。它能减缓提交任务的速度(因为主线程去干活了,没空提交新任务),起到负反馈/削峰的作用。 | 会阻塞主线程,影响系统吞吐量。 |
| DiscardPolicy | 直接丢弃任务,不做任何处理,也不抛异常。 | 适合无关紧要的任务(如某些非核心日志)。 | 任务丢失且无感知。 |
| DiscardOldestPolicy | 丢弃队列里等待最久的一个任务,然后把新任务加进去。 | 适合且希望能处理最新数据的场景。 | 会丢失旧数据。 |
四,线程的方法
1.,启动与执行 (生命周期核心)
| 方法名 | 类型 | 描述 | 关键点 |
|---|---|---|---|
start() |
实例 | 启动线程 。JVM 会调用底层的操作系统去申请资源,然后自动执行 run()。 |
只能调一次。再次调用会抛异常。 |
run() |
实例 | 业务逻辑。线程启动后真正执行的代码块。 | 不要直接调用 !直接调 run() 只是普通方法调用,不会启动新线程。 |
2.,流程控制 (暂停、等待、让步)
| 方法名 | 类型 | 描述 | 场景比喻 |
|---|---|---|---|
sleep(long millis) |
静态 | 休眠 。让当前正在执行的线程暂停一段时间,不释放锁。 | "稍微眯一会儿,但我占着坑不走。" |
join() |
实例 | 插队/等待 。调用 t.join() 的线程(通常是主线程)会卡住 ,直到线程 t 执行完毕。 |
"你先忙,忙完我再继续。" (常用于等待子线程结果) |
yield() |
静态 | 让步 。提示 CPU "我可以暂停一下,把机会让给其他同优先级的线程"。但 CPU 不一定理你。 | "我虽然不累,但可以发扬风格让给别人。" |
3, 线程协作 (Object 类的方法)
| 方法名 | 描述 | 关键区别 |
|---|---|---|
wait() |
等待 。让当前线程暂停,并释放锁(这点最重要),直到被唤醒。 | 必须在同步代码块中使用。 |
notify() |
唤醒。随机唤醒一个在对象上等待的线程。 | 就像班主任叫醒一个睡觉的学生。 |
notifyAll() |
全唤醒。唤醒所有在该对象上等待的线程。 | 就像下课铃响了,所有人都醒了。 |
Thread.sleep()``Object.wait()``Thread.join()区别
| 特性 | Thread.sleep() | Object.wait() | Thread.join() |
|---|---|---|---|
| 所属类 | Thread (静态方法) |
Object (成员方法) |
Thread (成员方法) |
| 锁的控制 (最重要!) | 死抱锁 🔒 (不释放锁) | 撒手锁 🔓 (释放锁,给别人机会) | 释放对象锁 (基于 wait 实现,释放被 join 线程对象的锁) |
| 使用位置 | 任何代码块 | 必须在 synchronized 块中 | 任何代码块 |
| 唤醒条件 | 时间到了 | 别人调 notify() / notifyAll() |
被 join 的线程执行完毕 (死掉) |
| 主要用途 | 暂停/延迟/模拟耗时 | 线程间通信 (生产者消费者) | 控制执行顺序 (T1->T2->T3) |
五,线程同步
关键字:synchronized
**1,**同步方法(给整个方法加锁)
这种方式直接在方法声明前添加synchronized关键字,锁的范围是整个方法体。当一个线程进入该方法时,它会自动获取对象实例的锁(对于实例方法)或类的锁(对于静态方法),其他线程必须等待锁释放才能进入。
java
public class Counter {
private int count = 0;
// 同步方法:整个方法加锁
public synchronized void increment() {
count++; // 临界区代码
}
public int getCount() {
return count;
}
}
**2.**同步代码块(给关键代码加锁,更灵活)
这种方式使用synchronized块,指定一个对象作为锁,只锁定需要同步的代码段,而不是整个方法。这提供了更高的灵活性,因为锁的范围更精确
java
public class Counter {
private int count = 0;
private final Object lock = new Object(); // 锁对象
public void increment() {
// 同步代码块:只锁定关键部分
synchronized(lock) {
count++; // 临界区代码
}
}
public int getCount() {
return count;
}
}
**3.**ReentrantLock(可重入锁,更灵活的显式锁)
比synchronized更灵活。它支持可重入性(同一线程可以多次获取同一锁),并允许更细粒度的控制,如尝试获取锁、超时机制、公平锁策略等。
java
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock(); // 创建ReentrantLock实例
public void increment() {
lock.lock(); // 获取锁
try {
count++; // 临界区代码
} finally {
lock.unlock(); // 确保在finally块中释放锁
}
}
public int getCount() {
return count;
}
}
在这个例子中,lock.lock()和lock.unlock()显式管理锁,确保线程安全。如果使用tryLock(),还可以处理无法获取锁的情况。
| synchronized和ReentrantLock比较 |
|---|
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现层面 | JVM 关键字 (自动挡) | JDK 类 (手动挡) |
| 释放锁 | 自动释放 (代码执行完或异常时) | 必须在 finally 中手动 unlock() |
| 灵活性 | 只能死等,不可中断 | 可尝试 (tryLock)、可中断、可超时 |
| 公平性 | 非公平 | 默认非公平,可设为公平 |
| 推荐场景 | 简单的同步,代码量少 | 复杂的并发控制,需要高级功能 |