Java并发编程是后端开发的核心技能之一,通过实战项目练习能更深刻地理解线程协作、锁机制、线程池等核心概念,避免纸上谈兵。下面推荐几个不同难度的练手项目,覆盖从基础到进阶的并发知识点。

基础入门级项目:多线程文件下载器
这个项目适合刚掌握线程基础概念的开发者,核心需求是将一个大文件拆分成多个片段,用多个线程同时下载不同片段,最后合并成完整文件,能练习线程创建、线程等待、共享变量控制等基础知识点。
核心实现思路
- 计算文件总大小,按线程数拆分下载区间
- 每个线程负责下载指定区间的文件内容,写入临时文件
- 主线程等待所有下载线程完成,合并所有临时文件
- 使用
CountDownLatch控制线程等待,避免主线程提前结束
核心代码示例
import java.io.*;
import java.net.URL;
import java.util.concurrent.CountDownLatch;
public class MultiThreadDownloader {
// 线程数量
private static final int THREAD_NUM = 3;
// 下载链接
private static final String FILE_URL = "http://ipipp.com/test.zip";
// 保存路径
private static final String SAVE_PATH = "test.zip";
public static void main(String[] args) throws Exception {
// 获取文件总大小
URL url = new URL(FILE_URL);
long fileSize = url.openConnection().getContentLengthLong();
long partSize = fileSize / THREAD_NUM;
CountDownLatch latch = new CountDownLatch(THREAD_NUM);
// 创建下载线程
for (int i = 0; i < THREAD_NUM; i++) {
long start = i * partSize;
long end = (i == THREAD_NUM - 1) ? fileSize - 1 : (i + 1) * partSize - 1;
new Thread(new DownloadTask(url, start, end, i, latch)).start();
}
// 等待所有线程下载完成
latch.await();
// 合并临时文件
mergeFiles(THREAD_NUM, SAVE_PATH);
System.out.println("下载完成");
}
static class DownloadTask implements Runnable {
private URL fileUrl;
private long start;
private long end;
private int index;
private CountDownLatch latch;
public DownloadTask(URL fileUrl, long start, long end, int index, CountDownLatch latch) {
this.fileUrl = fileUrl;
this.start = start;
this.end = end;
this.index = index;
this.latch = latch;
}
@Override
public void run() {
try (RandomAccessFile raf = new RandomAccessFile("part_" + index + ".tmp", "rw");
InputStream is = fileUrl.openStream()) {
// 跳过已下载的部分
long skipped = is.skip(start);
if (skipped != start) {
throw new IOException("跳过字节数不匹配");
}
byte[] buffer = new byte[1024];
long downloaded = 0;
int len;
while (downloaded < (end - start + 1) && (len = is.read(buffer)) != -1) {
int writeLen = (int) Math.min(len, end - start + 1 - downloaded);
raf.write(buffer, 0, writeLen);
downloaded += writeLen;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
}
private static void mergeFiles(int threadNum, String savePath) throws IOException {
try (RandomAccessFile out = new RandomAccessFile(savePath, "rw")) {
for (int i = 0; i < threadNum; i++) {
try (RandomAccessFile part = new RandomAccessFile("part_" + i + ".tmp", "r")) {
byte[] buffer = new byte[1024];
int len;
while ((len = part.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
// 删除临时文件
new File("part_" + i + ".tmp").delete();
}
}
}
}
进阶级项目:简易线程池实现
这个项目适合已经掌握基础并发知识,想要深入理解线程池运行机制的开发者,核心需求是手动实现一个简化版的线程池,支持提交任务、执行任务、动态调整核心线程数等功能,能练习线程管理、阻塞队列、任务调度等知识点。
核心功能设计
- 核心线程数和最大线程数可配置
- 使用阻塞队列缓存待执行任务
- 支持拒绝策略,当队列满且线程数达到最大值时处理新提交的任务
- 线程空闲超时回收机制
核心代码示例
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class SimpleThreadPool {
// 核心线程数
private final int corePoolSize;
// 最大线程数
private final int maxPoolSize;
// 任务队列
private final BlockingQueue<Runnable> taskQueue;
// 当前工作线程列表
private final List<Worker> workers;
// 线程编号生成器
private final AtomicInteger threadNum = new AtomicInteger(0);
// 是否关闭线程池
private volatile boolean isShutdown = false;
public SimpleThreadPool(int corePoolSize, int maxPoolSize, int queueCapacity) {
this.corePoolSize = corePoolSize;
this.maxPoolSize = maxPoolSize;
this.taskQueue = new ArrayBlockingQueue<>(queueCapacity);
this.workers = new ArrayList<>();
}
// 提交任务
public void execute(Runnable task) {
if (isShutdown) {
throw new IllegalStateException("线程池已关闭");
}
// 如果当前线程数小于核心线程数,创建新线程
if (workers.size() < corePoolSize) {
addWorker(task);
return;
}
// 尝试放入队列
if (taskQueue.offer(task)) {
return;
}
// 队列满了,尝试创建非核心线程
if (workers.size() < maxPoolSize) {
addWorker(task);
return;
}
// 达到最大线程数,执行拒绝策略
reject(task);
}
// 添加工作线程
private synchronized void addWorker(Runnable firstTask) {
if (workers.size() >= maxPoolSize) {
return;
}
Worker worker = new Worker(firstTask);
Thread thread = new Thread(worker, "pool-thread-" + threadNum.incrementAndGet());
worker.thread = thread;
workers.add(worker);
thread.start();
}
// 拒绝策略
private void reject(Runnable task) {
System.out.println("任务被拒绝:" + task);
}
// 关闭线程池
public void shutdown() {
isShutdown = true;
for (Worker worker : workers) {
worker.thread.interrupt();
}
}
// 工作线程类
class Worker implements Runnable {
Thread thread;
private Runnable firstTask;
Worker(Runnable firstTask) {
this.firstTask = firstTask;
}
@Override
public void run() {
// 先执行第一个任务,再从队列取任务
Runnable task = firstTask;
try {
while (task != null || (task = taskQueue.poll(1, TimeUnit.SECONDS)) != null) {
try {
task.run();
} catch (Exception e) {
e.printStackTrace();
}
task = null;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 线程退出,从工作列表移除
synchronized (SimpleThreadPool.this) {
workers.remove(this);
}
}
}
}
// 测试示例
public static void main(String[] args) {
SimpleThreadPool pool = new SimpleThreadPool(2, 4, 10);
for (int i = 0; i < 15; i++) {
int taskId = i;
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务:" + taskId);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 等待任务执行完成
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.shutdown();
}
}
高阶级项目:简易秒杀系统
这个项目适合已经掌握并发核心知识,想要练习高并发场景处理的开发者,核心需求是模拟商品秒杀场景,解决超卖、库存扣减并发安全、请求削峰等问题,能练习锁机制、分布式锁、限流、缓存等综合知识点。
核心难点与解决方案
- 库存超卖问题:使用
synchronized或者ReentrantLock保证单JVM下库存扣减的原子性,分布式场景可使用Redis分布式锁 - 高并发请求处理:使用消息队列削峰,将秒杀请求异步处理,避免直接冲击数据库
- 重复下单问题:使用用户ID和商品ID作为唯一键,防止同一用户重复秒杀同一商品
核心库存扣减代码示例
import java.util.concurrent.locks.ReentrantLock;
public class SeckillService {
// 商品库存
private int stock = 100;
// 可重入锁,保证库存扣减的线程安全
private final ReentrantLock lock = new ReentrantLock();
// 秒杀方法
public boolean seckill(int userId) {
lock.lock();
try {
// 检查库存
if (stock <= 0) {
System.out.println("用户" + userId + "秒杀失败,库存不足");
return false;
}
// 模拟扣减库存的业务逻辑
stock--;
System.out.println("用户" + userId + "秒杀成功,剩余库存:" + stock);
// 后续可添加订单创建、异步通知等逻辑
return true;
} finally {
lock.unlock();
}
}
// 测试示例
public static void main(String[] args) {
SeckillService service = new SeckillService();
// 模拟100个用户同时秒杀
for (int i = 0; i < 100; i++) {
int userId = i;
new Thread(() -> service.seckill(userId)).start();
}
}
}
项目选择建议
如果是刚接触Java并发编程,优先选择多线程文件下载器,先掌握线程的基本使用和协作方式;如果已经熟悉基础概念,想要深入理解线程池原理,可以选择简易线程池实现;如果已经掌握大部分并发知识,想要练习高并发场景的问题解决,可以选择简易秒杀系统。练习过程中可以逐步扩展项目功能,比如给文件下载器添加断点续传,给线程池添加更多拒绝策略,给秒杀系统添加限流和缓存功能,进一步提升实战能力。