🔥关注墨瑾轩,带你探索编程的奥秘!🚀
🔥超萌技术攻略,轻松晋级编程高手🚀
🔥技术宝库已备好,就等你来挖掘🚀
🔥订阅墨瑾轩,智趣学习不孤单🚀
🔥即刻启航,编程之旅更有趣🚀
解决方案:
- 核心1:线程安全策略(防弹衣)
- 核心2:线程池调优(流水线)
- 核心3:并发工具类(瑞士军刀)
- 陷阱1:死锁(线程打架)
- 陷阱2:资源泄漏(内存黑洞)
- 陷阱3:活锁(永不停歇的“死循环”)
- 陷阱4:饥饿(线程“饿死”)
- 陷阱5:竞态条件(数据“变脸”)
** 3大核心+5大陷阱的实战指南**
第一步:核心1——线程安全策略:代码的“防弹衣”
核心思想:让多线程访问共享数据时“井然有序”
代码示例1:不可变对象
// 不可变Person类
public final class ImmutablePerson {
private final String name;
private final int age;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
注释解析:
- 不可变对象优势:
- 创建后状态不可修改,像“一次性包装盒”一样安全
- 无需同步即可安全共享,像“只读字典”一样放心
- 实战技巧:
- 所有字段设为
final
- 通过构造方法初始化
- 避免提供
set
方法
- 所有字段设为
代码示例2:线程封闭
// ThreadLocal示例:每个线程独立的SimpleDateFormat
public class ThreadLocalExample {
private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormatThreadLocal.get().format(date);
}
}
注释解析:
- 线程封闭原理:
- 数据仅被单个线程访问,像“私人保险箱”一样安全
ThreadLocal
为每个线程提供独立副本
- 实战技巧:
- 适用于线程内共享但线程间隔离的变量
- 注意清理
ThreadLocal
,避免内存泄漏
代码示例3:同步容器类
// 同步List示例
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
注释解析:
- 同步容器类原理:
- 通过
Collections.synchronizedList
包装集合 - 每个操作都加锁,像“排队上厕所”一样有序
- 通过
- 实战技巧:
- 适用于简单同步需求
- 高并发下性能不如
ConcurrentHashMap
第二步:核心2——线程池调优:代码的“流水线”
核心思想:复用线程资源,提升性能
代码示例1:动态调整线程池参数
// 动态调整线程池参数
int corePoolSize = Runtime.getRuntime().availableProcessors(); // CPU核心数
int maxPoolSize = corePoolSize * 2; // 最大线程数
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(1000); // 任务队列
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy(); // 拒绝策略
ExecutorService executor = new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
60, TimeUnit.SECONDS,
workQueue,
handler
);
注释解析:
- 线程池参数说明:
corePoolSize
:核心线程数(如CPU核心数)maxPoolSize
:最大线程数(如核心数×2)workQueue
:任务队列(如1000个任务)handler
:拒绝策略(如调用者执行)
- 实战技巧:
- CPU密集型任务:线程数≈CPU核心数
- IO密集型任务:线程数≈CPU核心数×2
- 任务队列:避免无限队列导致内存溢出
代码示例2:任务提交与关闭
// 提交任务
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
try {
Thread.sleep(100); // 模拟任务
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
注释解析:
- 任务提交:
submit()
提交任务,返回Future
execute()
直接执行任务(不推荐)
- 关闭线程池:
shutdown()
:停止接收新任务,等待旧任务完成shutdownNow()
:强制关闭,中断所有任务
第三步:核心3——并发工具类:代码的“瑞士军刀”
核心思想:用现成工具简化并发逻辑
代码示例1:ConcurrentHashMap
// ConcurrentHashMap示例
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 1);
map.computeIfAbsent("banana", k -> 2); // 原子操作
注释解析:
- ConcurrentHashMap优势:
- 分段锁设计(Java 7)或CAS+synchronized(Java 8+)
- 高并发下性能远优于
Hashtable
- 实战技巧:
- 用
computeIfAbsent
避免重复计算 - 用
forEach
遍历(弱一致性)
- 用
代码示例2:Semaphore
// Semaphore示例:控制同时访问资源的线程数
Semaphore semaphore = new Semaphore(3); // 允许3个线程同时访问
public void accessResource() {
try {
semaphore.acquire(); // 获取许可
System.out.println("Accessing resource...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
}
注释解析:
- Semaphore作用:
- 控制资源访问数量(如数据库连接池)
acquire()
获取许可,release()
释放许可
- 实战技巧:
- 用
tryAcquire(timeout)
避免永久阻塞 - 用
availablePermits()
查看可用许可数
- 用
第四步:陷阱1——死锁:线程的“打架”
核心思想:避免线程互相等待
代码示例1:死锁场景
// 死锁示例
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock1) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread 1 done");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread 2 done");
}
}
});
t1.start();
t2.start();
注释解析:
- 死锁原理:
- 线程1持有
lock1
,等待lock2
- 线程2持有
lock2
,等待lock1
- 线程1持有
- 解决方法:
- 按固定顺序获取锁
- 使用
tryLock()
设置超时时间
代码示例2:检测死锁
// 检测死锁
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
System.out.println("检测到死锁线程:");
for (long threadId : deadlockedThreads) {
System.out.println(Thread.currentThread().getName());
}
}
注释解析:
- 死锁检测:
- 通过
ThreadMXBean
获取死锁线程 - 打印线程堆栈跟踪
- 通过
- 实战技巧:
- 使用JDK工具(如
jstack
)分析线程转储 - 在IDE中可视化线程交互
- 使用JDK工具(如
第五步:陷阱2——资源泄漏:内存的“黑洞”
核心思想:及时释放资源
代码示例1:资源泄漏场景
// 资源泄漏示例(未关闭文件流)
FileInputStream fis = new FileInputStream("file.txt");
byte[] data = new byte[1024];
fis.read(data);
// 未关闭流,导致资源泄漏
注释解析:
- 资源泄漏原理:
- 文件流、数据库连接等资源未关闭
- 内存持续增长,最终导致OOM
- 解决方法:
- 使用
try-with-resources
自动关闭资源 - 用
finally
块手动关闭
- 使用
代码示例2:正确关闭资源
// 正确关闭文件流
try (FileInputStream fis = new FileInputStream("file.txt")) {
byte[] data = new byte[1024];
fis.read(data);
} catch (IOException e) {
e.printStackTrace();
}
注释解析:
- try-with-resources:
- 自动关闭实现
AutoCloseable
的资源 - 避免忘记关闭导致泄漏
- 自动关闭实现
- 实战技巧:
- 对数据库连接、线程池等资源做同样处理
- 使用内存分析工具(如VisualVM)检测泄漏
第六步:陷阱3——活锁:永不停歇的“死循环”
核心思想:避免线程“永不停歇”
代码示例1:活锁场景
// 活锁示例:两个线程不断退让
AtomicBoolean flag = new AtomicBoolean(true);
Thread t1 = new Thread(() -> {
while (true) {
if (flag.get()) {
System.out.println("Thread 1 doing work");
flag.set(false);
} else {
System.out.println("Thread 1 waiting");
}
}
});
Thread t2 = new Thread(() -> {
while (true) {
if (!flag.get()) {
System.out.println("Thread 2 doing work");
flag.set(true);
} else {
System.out.println("Thread 2 waiting");
}
}
});
t1.start();
t2.start();
注释解析:
- 活锁原理:
- 线程不断尝试重试,但永远无法前进
- 像“拔河比赛”一样僵持
- 解决方法:
- 引入随机延时
- 使用非阻塞算法
代码示例2:解决活锁
// 引入随机延时
Thread t1 = new Thread(() -> {
while (true) {
if (flag.get()) {
System.out.println("Thread 1 doing work");
flag.set(false);
} else {
try {
Thread.sleep((long) (Math.random() * 100)); // 随机延时
} catch (InterruptedException e) {}
}
}
});
注释解析:
- 随机延时:
- 打破线程之间的“默契”
- 让某个线程有机会先执行
- 实战技巧:
- 在重试逻辑中加入退避策略
- 使用
ReentrantLock.tryLock()
替代synchronized
第七步:陷阱4——饥饿:线程的“饿死”
核心思想:确保所有线程公平访问资源
代码示例1:饥饿场景
// 饥饿示例:低优先级线程无法获取CPU时间
Thread highPriorityThread = new Thread(() -> {
while (true) {
// 占用CPU
}
});
highPriorityThread.setPriority(Thread.MAX_PRIORITY);
highPriorityThread.start();
Thread lowPriorityThread = new Thread(() -> {
System.out.println("Low priority thread running");
});
lowPriorityThread.setPriority(Thread.MIN_PRIORITY);
lowPriorityThread.start();
注释解析:
- 饥饿原理:
- 高优先级线程长期占用资源
- 低优先级线程无法获得执行机会
- 解决方法:
- 使用公平锁(如
ReentrantLock(true)
) - 避免无限制的高优先级线程
- 使用公平锁(如
代码示例2:公平锁
// 公平锁示例
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
Thread t1 = new Thread(() -> {
fairLock.lock();
try {
System.out.println("Thread 1 acquired lock");
} finally {
fairLock.unlock();
}
});
Thread t2 = new Thread(() -> {
fairLock.lock();
try {
System.out.println("Thread 2 acquired lock");
} finally {
fairLock.unlock();
}
});
t1.start();
t2.start();
注释解析:
- 公平锁:
- 按请求顺序分配锁
- 避免线程“饿死”
- 实战技巧:
- 在资源竞争激烈时启用公平锁
- 用
tryLock()
替代lock()
避免阻塞
第八步:陷阱5——竞态条件:数据的“变脸”
核心思想:避免共享数据被多个线程修改
代码示例1:竞态条件场景
// 竞态条件示例:未同步的计数器
class Counter {
private int count = 0;
public void increment() {
count++; // 非原子操作
}
public int getCount() {
return count;
}
}
Counter counter = new Counter();
Thread t1 = new Thread(counter::increment);
Thread t2 = new Thread(counter::increment);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + counter.getCount()); // 结果可能不是2
注释解析:
- 竞态条件原理:
count++
是三个操作:读、加、写- 多线程下可能覆盖修改
- 解决方法:
- 使用
synchronized
加锁 - 用
AtomicInteger
替代普通变量
- 使用
代码示例2:解决竞态条件
// 使用synchronized
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
注释解析:
- synchronized:
- 确保同一时间只有一个线程修改数据
- 像“红绿灯”一样控制通行
- 实战技巧:
- 用
ReentrantLock
替代synchronized
(更灵活) - 用
AtomicInteger
处理简单计数
- 用
进阶技巧:3大核心+5大陷阱的组合拳
组合1:线程安全+线程池+并发工具
场景:高并发任务调度
// 使用线程池+ConcurrentHashMap
ExecutorService executor = Executors.newFixedThreadPool(4);
ConcurrentHashMap<String, Integer> resultMap = new ConcurrentHashMap<>();
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
String key = "task-" + Thread.currentThread().getId();
resultMap.put(key, 1); // 原子操作
});
}
注释解析:
- 线程池:复用线程
- ConcurrentHashMap:线程安全的映射
- 实战技巧:
- 用
CompletableFuture
处理异步任务 - 用
Phaser
替代CountDownLatch
(更灵活)
- 用
组合2:死锁检测+资源泄漏+活锁
场景:自动化检测并修复问题
// 自动检测死锁
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
System.out.println("检测到死锁,尝试重启线程池...");
executor.shutdownNow();
executor = Executors.newFixedThreadPool(4);
}
注释解析:
- 自动检测:
- 定期扫描死锁线程
- 自动重启线程池
- 实战技巧:
- 用AOP记录线程状态
- 用监控工具(如Prometheus)实时报警
* 用3大核心+5大陷阱打造“防弹”并发代码**
还记得那个被并发问题折磨得“头秃”的你吗?现在你已经掌握了:
✅ 3大核心:线程安全、线程池调优、并发工具类
✅ 5大陷阱:死锁、资源泄漏、活锁、饥饿、竞态条件