一。并发、并行、串行
并发:宏观上并行,微观上串行
并行:一起执行
串行:交替执行
二。创建线程的几种方式
1. 继承Thread类(最基础)
// 1. 继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行: " + Thread.currentThread().getName());
}
}
// 使用
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程,自动调用run()方法
// 输出: 线程执行: Thread-0
}
}
2. 实现Runnable接口(推荐)
// 2. 实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable线程: " + Thread.currentThread().getName());
}
}
// 使用
public class RunnableDemo {
public static void main(String[] args) {
// 方式1: 单独类
Thread thread1 = new Thread(new MyRunnable());
thread1.start();
// 方式2: 匿名内部类
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名Runnable线程");
}
});
thread2.start();
// 方式3: Lambda表达式(Java 8+)
Thread thread3 = new Thread(() -> {
System.out.println("Lambda线程: " + Thread.currentThread().getName());
});
thread3.start();
}
}
3. 实现Callable接口 + FutureTask(有返回值)
// 3. 实现Callable接口(带返回值)
class MyCallable implements Callable<String> {
private final String taskName;
public MyCallable(String taskName) {
this.taskName = taskName;
}
@Override
public String call() throws Exception {
Thread.sleep(1000); // 模拟耗时操作
return taskName + " 完成,线程: " + Thread.currentThread().getName();
}
}
// 使用
public class CallableDemo {
public static void main(String[] args) throws Exception {
Callable<String> callable = new MyCallable("任务1");
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
// 获取返回值(会阻塞直到任务完成)
String result = futureTask.get();
System.out.println("结果: " + result);
// 输出: 结果: 任务1 完成,线程: Thread-0
}
}
三。线程池
1.几种线程池类型
public class ThreadPoolTypes {
public static void main(String[] args) {
// 5.1 固定大小线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
// 5.2 缓存线程池(自动扩容收缩)
//来任务自动创建线程,没任务线程存活时间到了就自己销毁
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 5.3 单线程池(保证顺序执行)
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
// 5.4 定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);
// 定时任务示例
scheduledPool.schedule(() -> {
System.out.println("延迟3秒执行");
}, 3, TimeUnit.SECONDS);
// 周期性任务
scheduledPool.scheduleAtFixedRate(() -> {
System.out.println("每2秒执行一次");
}, 1, 2, TimeUnit.SECONDS);
}
}
2.线程池提交任务
a.无返回值execute提交
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交Runnable任务(无返回值)
executor.execute(() -> {
System.out.println("执行任务: " + Thread.currentThread().getName());
// 模拟业务逻辑
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 可以连续提交多个任务
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("任务" + taskId + " 在 " +
Thread.currentThread().getName() + " 执行");
});
}
b.有返回值submit提交
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交Callable任务(有返回值)
Future<String> future = executor.submit(() -> {
System.out.println("Callable任务开始执行");
Thread.sleep(2000); // 模拟耗时操作
return "任务完成结果: " + Thread.currentThread().getName();
});
// 继续执行其他逻辑(不阻塞)
System.out.println("主线程继续执行...");
try {
// 需要结果时获取(会阻塞)
String result = future.get(); // 阻塞直到任务完成
System.out.println("获取结果: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
3.线程池7个参数
好的,这是 Java 线程池七大参数的总结表格,包含参数名、含义和一个形象的比喻。
Java 线程池七大参数一览表
|
参数名 |
数据类型 |
含义 |
通俗比喻 |
|---|---|---|---|
|
核心线程数 |
|
线程池中保持常驻的核心线程数量,即使它们处于空闲状态也不会被回收(除非设置 |
公司的正式员工 |
|
最大线程数 |
|
线程池允许创建的最大线程数量。核心线程用完后,任务队列也满了,会创建新线程直到达到此上限。 |
公司最大员工数(正式+临时) |
|
线程存活时间 |
|
当线程数超过 |
临时工的空闲等待时间 |
|
时间单位 |
|
|
存活时间的单位(如:小时) |
|
工作队列 |
|
用于保存等待执行的任务的队列。这是一个关键的缓冲区。 |
产品待办需求列表 |
|
线程工厂 |
|
用于创建新线程的工厂。不同类型的工厂可以用于设置线程名、优先级、守护线程等,便于监控和调试。 |
HR / 招聘部门 |
|
拒绝策略 |
|
当线程池和队列都已饱和,无法处理新任务时,执行的拒绝策略。(直接抛出异常,由提交任务的线程完成,静默丢弃,丢弃最旧的任务添加新任务) |
公司满负荷时的接单策略 |
四。sleep、wait 、notify、notifyall、join、yield
调用wait会进入等待池,需要notify()或者notifyall唤醒。
|
方法 |
所属类 |
作用 | 释放cpu |
释放锁 |
唤醒条件 |
使用场景 |
|---|---|---|---|---|---|---|
|
|
Thread |
线程休眠 | 释放,时间到结束 |
❌ 不释放 |
时间到期 |
定时任务 |
|
|
Object |
线程等待 | 释放,被唤醒结束 |
✅ 释放 |
notify/超时 |
线程间协作 |
|
|
Object |
唤醒单个线程 |
- |
- |
线程间通信 | |
|
|
Object |
唤醒所有线程 |
- |
- |
多线程协作 | |
|
|
Thread |
等待线程结束 |
- |
线程终止 |
线程顺序执行 | |
|
|
Thread |
让出CPU,不一定能让出去 |
❌ 不释放 |
立即让出 |
线程优先级调整 |
五。常见代码
1.多个线程交替打印0-100
public class AlternatePrint {
private static final Object lock = new Object();
private static int count = 0;
private static final int MAX = 100;
public static void main(String[] args) {
new Thread(() -> {
while (count <= MAX) {
synchronized (lock) {
if (count % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + count++);
lock.notify();
} else {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}, "线程A").start();
new Thread(() -> {
while (count <= MAX) {
synchronized (lock) {
if (count % 2 == 1) {
System.out.println(Thread.currentThread().getName() + ": " + count++);
lock.notify();
} else {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}, "线程B").start();
}
}
五.锁、同步相关
1.相关概念:
可重入:同一个线程可以多次获取同一把锁。
public class ReentrantExample {
public synchronized void methodA() {
System.out.println("进入methodA");
methodB(); // 同一个线程再次获取锁
System.out.println("离开methodA");
}
public synchronized void methodB() {
System.out.println("进入methodB");
// 可以正常执行,不会死锁
System.out.println("离开methodB");
}
}
公平锁:维护一个线程等待队列来实现。按照线程请求锁的顺序分配(先到先得)
非公平锁:不保证顺序,可能允许新请求的线程插队,公平和非公平只体现在新线程是否能竞争锁,但是如果有线程让出来cpu资源,队列线程唤醒了还是第一个。
偏向锁:JVM 对 synchronized 的一种优化机制,当一个线程第一次获取偏向锁时,JVM 会记录该线程的 ID。如果该线程再次请求锁,JVM 会直接允许它获取锁,而无需进行任何同步操作.,就是减少了不断的CAS自旋开销,只在第一个获取锁的过程中判断。如果有其他线程尝试获取锁,偏向锁会升级为轻量级锁,用CAS来实现,避免线程阻塞。当更多线程竞争锁资源,轻量级锁会转向重量级锁,用互斥来实现。会导致线程阻塞。
可中断锁:lock.lockInterruptibly()。可中断锁允许线程在等待锁的过程中被中断。
|
方法 |
类型 |
作用对象 |
返回值 |
是否清除中断状态 |
典型使用场景 |
|---|---|---|---|---|---|
|
|
实例方法 |
目标线程(thread) |
void |
不涉及 |
主动请求中断另一个线程 |
|
|
静态方法 |
当前线程 |
boolean |
会清除 |
检查并清除当前线程的中断状态(一次性检查) |
|
|
实例方法 |
目标线程(thread) |
boolean |
不会清除 |
持续监控某线程的中断状态 |
Thread thread = new Thread(() -> {
while (true) {
System.out.println("Running..."); // 不检查中断标志
}
});
thread.start();
thread.interrupt(); // 线程不会停止!
=======================================================================================
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) { // 检查中断标志
System.out.println("Working...");
}
System.out.println("线程收到中断请求,优雅退出");
});
thread.start();//主线程调用子线程的执行,二者会同时执行
thread.interrupt(); // 线程会退出,此时也是主线程中断子线程

超时锁:lock.tryLock(long, TimeUnit),超时锁允许线程在尝试获取锁时设置一个超时时间。如果在该时间内未能获取锁,线程会放弃等待并继续执行其他操作。
自旋锁:线程获取锁的过程中,当没获取到,就循环获取。通过CAS进行完成的。不会阻塞线程。
注意:CAS跟自旋锁是两个东西,自旋锁可以说是使用了CAS,没改变就循环获取继续用CAS。
AQS:用于构建锁和其他同步器(如 ReentrantLock、Semaphore、CountDownLatch 等), 提供了一个框架,开发者可以基于它实现自定义的同步器。
- 使用一个 共享的同步状态(state) 来表示锁的状态。
- 通过一个双向链表组成的 FIFO(先进先出的) 队列 管理等待获取锁的线程。
- 使用CAS操作来获取锁(改变锁的状态)。
- 共享模式:允许多个线程同时获取锁。
- 独占模式:只允许一个线程获取锁。
volatile: Java 中的一个关键字,主要用于解决 可见性 和 指令重排序 问题:
-
保证可见性:当一个线程修改了
volatile变量的值,其他线程可以立即看到修改后的值。 -
禁止指令重排序:
volatile变量的读写操作不会被 JVM 重排序,从而保证程序的执行顺序符合预期。 -
并不能替代锁,它只能保证可见性和禁止指令重排序,但无法保证原子性
volatile变量的读写操作直接作用于主内存,而不是线程的本地内存。- 每次读取
volatile变量时,线程都会从主内存中获取最新值。 - 每次写入
volatile变量时,线程会立即将新值刷新到主内存中。
2.CAS
是一种原子操作,用于在多线程环境下实现无锁的线程安全操作。CAS操作通常用于实现非阻塞算法。
CAS操作包含三个操作数:
- 内存位置(V):需要更新的变量。
- 预期值(A):操作之前读取的变量值。
- 新值(B):如果内存位置的值等于预期值,则将其更新为新值。
CAS的操作逻辑如下:
- 比较内存位置的值与预期值,如果相等,则将内存位置的值更新为新值。
- 如果不相等,则不做任何操作。
- 无论是否更新成功,CAS操作都会返回内存位置的当前值。
AtomicInteger atomicInt = new AtomicInteger(0); // 预期值为0,新值为1 boolean success = atomicInt.compareAndSet(0, 1); System.out.println("CAS操作是否成功: " + success); // 输出 true // 预期值为0,新值为2(此时实际值为1,所以操作失败) success = atomicInt.compareAndSet(0, 2); System.out.println("CAS操作是否成功: " + success); // 输出 false
AtomicInteger:原子整型类,它能够在多线程环境下保证对整型变量的操作具有原子性。
3.Synchronized与ReentrantLock
Synchronized:是一个关键字,修饰代码块(对象)、静态方法(类)、成员方法(对象)。
- 无需手动管理锁。
- 自动释放锁。
- 非公平锁
- 可重入:同一个线程可以多次获取同一把锁。
- 不支持高级功能:可中断锁、超时锁等。
- 原理:
ReentrantLock :
ReentrantLock是一个类,实现了Lock接口。修饰代码块的。- 它是显式锁,需要手动获取和释放锁。
- 可设置公平锁或者非公平锁
- 提供了比
synchronized更高级的功能,如可中断锁、超时锁等。 - 可重入:同一个线程可以多次获取同一把锁。
- 原理:
synchronized和ReentrantLock都能锁什么?
synchronized:锁类方法、实例方法、object对象
lock:锁一段代码块
4.CountDownLatch、CyclicBarrier、Semaphore的区别和底层原理
4.1、CountDownLatch
CountDownLatch的底层依赖于 AQS(AbstractQueuedSynchronizer),这是一个用于构建锁和同步器的框架。CountDownLatch通过 AQS 的共享模式来实现线程的等待和唤醒。- 常用于计数器来控制所有线程执行完毕:
CountDownLatch维护一个计数器,设置初始值,进行减法操作。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3); // 初始计数3
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 完成任务");
latch.countDown(); // 计数减1
}).start();
}
latch.await(); // 主线程等待计数归零
System.out.println("所有任务完成,主线程继续");
}
}
Thread-0 完成任务
Thread-1 完成任务
Thread-2 完成任务
所有子线程完成任务,主线程继续执行
4.2、CyclicBarrier
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达屏障,执行屏障动作");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 到达屏障");
barrier.await(); // 等待其他线程
System.out.println(Thread.currentThread().getName() + " 继续执行");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
Thread-0 到达屏障
Thread-1 到达屏障
Thread-2 到达屏障
所有线程到达屏障,执行屏障动作
Thread-2 继续执行
Thread-0 继续执行
Thread-1 继续执行
a、被动重置计数器
public class CyclicBarrierReuseDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(2, () -> {
System.out.println("屏障已触发,准备下一轮");
});
// 第一轮
new Thread(() -> awaitAtBarrier(barrier, "线程A-第一轮")).start();
new Thread(() -> awaitAtBarrier(barrier, "线程B-第一轮")).start();
// 第二轮(自动重用)
new Thread(() -> awaitAtBarrier(barrier, "线程A-第二轮")).start();
new Thread(() -> awaitAtBarrier(barrier, "线程B-第二轮")).start();
}
private static void awaitAtBarrier(CyclicBarrier barrier, String threadName) {
try {
System.out.println(threadName + " 到达屏障");
barrier.await();
System.out.println(threadName + " 冲破屏障");
} catch (Exception e) {
e.printStackTrace();
}
}
}
线程A-第一轮 到达屏障
线程B-第一轮 到达屏障
屏障已触发,准备下一轮
线程B-第一轮 冲破屏障
线程A-第一轮 冲破屏障
线程A-第二轮 到达屏障
线程B-第二轮 到达屏障
屏障已触发,准备下一轮
线程B-第二轮 冲破屏障
线程A-第二轮 冲破屏障
b、主动重置计数器
通过 reset()方法可强制重置屏障(中断当前轮次,所有等待线程收BrokenBarrierException)。
CyclicBarrier barrier = new CyclicBarrier(3);
new Thread(() -> {
try {
barrier.await();
} catch (BrokenBarrierException e) {
System.out.println("线程被中断:屏障已重置");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
barrier.reset(); // 强制重置
c、二者区别:
|
对比点 |
CountDownLatch |
CyclicBarrier |
|---|---|---|
|
核心目的 |
主等子 |
子等子 |
|
重用性 |
❌ 一次性 |
✅ 可循环 |
|
计数方向 |
递减( |
递增( |
|
异常影响 |
局部 |
全局(需重置) |
|
典型场景 |
启动检查、任务汇总 |
并行计算、回合制同步 |
4.3、Semaphore
用于 控制并发访问资源 的工具类。它通过维护一组 许可证(permits) 来限制同时访问资源的线程数量
Semaphore的底层依赖于 AQS(AbstractQueuedSynchronizer),Semaphore通过 AQS 的共享模式来实现许可证的获取和释放。- 通过控制许可证的获取、释放来控制并发线程的数量。
六、死锁产生的四个必要条件(缺一不可)
-
互斥条件:一个资源每次只能被一个线程使用。
-
请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
-
不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程强行剥夺
-
循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
只要打破以上四个条件中的任意一个,死锁就能被预防。
解决思路:
解决请求与保持、不可剥夺:使用可超时的锁(ReentrantLock.tryLock)
解决循环等待:避免嵌套加锁,设计时保证锁顺序一致(最有效、最常用),强制所有线程以全局统一的顺序获取锁。

被折叠的 条评论
为什么被折叠?



