【JavaSE全面教学】Java多线程与并发基础Day15(2026年)
写在前面这是JavaSE系列的最后一篇多线程是Java进阶的必经之路也是面试中区分初级和中高级开发者的分水岭。今天我们讲多线程的基础知识包括线程的创建、生命周期、同步机制和线程池。文章目录一、进程与线程1.1 基本概念1.2 为什么需要多线程二、创建线程的四种方式2.1 继承Thread类2.2 实现Runnable接口推荐2.3 实现Callable接口有返回值2.4 线程池生产环境推荐2.5 四种方式对比三、线程的生命周期3.1 六种状态3.2 状态转换示例四、线程同步4.1 为什么需要同步4.2 synchronized关键字4.3 Lock接口更灵活4.4 volatile关键字五、线程通信5.1 wait和notify5.2 sleep和wait的区别六、线程池6.1 为什么需要线程池6.2 创建线程池6.3 ThreadPoolExecutor推荐6.4 拒绝策略参考资料七、面试高频考点考点1start和run的区别考点2synchronized和Lock的区别考点3线程池的核心参数八、总结 JavaSE全面教学系列完结一、进程与线程1.1 基本概念进程Process - 操作系统分配资源的基本单位 - 每个进程有独立的内存空间 - 例如打开一个浏览器就是一个进程 线程Thread - CPU调度的基本单位 - 一个进程可以包含多个线程 - 线程共享进程的内存空间 - 例如浏览器中同时下载多个文件每个下载任务是一个线程1.2 为什么需要多线程实际场景想象一下你在浏览器里同时下载多个文件。如果是单线程必须等第一个文件下载完才能开始第二个。而多线程可以让多个下载任务同时进行大大缩短总时间。// 单线程任务串行执行// 任务13秒→ 任务22秒→ 任务35秒 总共10秒// 多线程任务并行执行// 任务13秒// 任务22秒 → 总共5秒取决于最慢的任务// 任务35秒// 好处// 1. 提高CPU利用率// 2. 提高程序响应速度// 3. 充分利用多核CPU经验之谈在实际项目中多线程最常见的应用场景是批量数据处理、异步任务执行、定时任务调度。但也要注意线程不是越多越好过多的线程会导致上下文切换开销增大反而降低性能。二、创建线程的四种方式2.1 继承Thread类// 方式1继承Thread类重写run方法classMyThreadextendsThread{Overridepublicvoidrun(){for(inti0;i5;i){System.out.println(子线程i);}}}// 启动线程publicclassTest{publicstaticvoidmain(String[]args){MyThreadtnewMyThread();t.start();// 启动线程不是runfor(inti0;i5;i){System.out.println(主线程i);}}}// 注意调用start()才是启动线程// 调用run()只是普通方法调用还是在主线程执行踩坑提醒新手最容易犯的错误就是调用run()而不是start()。调用run()不会创建新线程只是在当前线程执行方法体完全达不到多线程的效果。2.2 实现Runnable接口推荐// 方式2实现Runnable接口classMyRunnableimplementsRunnable{Overridepublicvoidrun(){for(inti0;i5;i){System.out.println(子线程i);}}}// 启动线程publicclassTest{publicstaticvoidmain(String[]args){ThreadtnewThread(newMyRunnable());t.start();}}// 方式3Lambda表达式Java 8最简洁newThread(()-{System.out.println(子线程运行);}).start();2.3 实现Callable接口有返回值importjava.util.concurrent.*;// 方式4实现Callable接口可以返回结果classMyCallableimplementsCallableInteger{OverridepublicIntegercall()throwsException{intsum0;for(inti1;i100;i){sumi;}returnsum;// 返回结果}}publicclassTest{publicstaticvoidmain(String[]args)throwsException{// 创建FutureTaskFutureTaskIntegertasknewFutureTask(newMyCallable());// 启动线程newThread(task).start();// 获取结果会阻塞直到线程执行完毕Integerresulttask.get();System.out.println(1到100的和result);// 5050}}2.4 线程池生产环境推荐经验之谈在实际项目中千万不要用new Thread()创建线程线程创建和销毁是有开销的而且线程数不可控可能导致OOM。线程池可以复用线程、控制并发数是生产环境的标准做法。importjava.util.concurrent.*;// 创建线程池ExecutorServicepoolExecutors.newFixedThreadPool(3);// 3个线程的固定线程池// 提交任务pool.execute(()-System.out.println(任务1));pool.execute(()-System.out.println(任务2));pool.execute(()-System.out.println(任务3));// 关闭线程池pool.shutdown();2.5 四种方式对比方式优点缺点推荐度继承Thread简单直接Java单继承限制★★☆☆☆实现Runnable灵活可继承其他类无返回值★★★★☆实现Callable有返回值稍复杂★★★★☆线程池复用线程性能好需要理解线程池原理★★★★★三、线程的生命周期3.1 六种状态NEW新建 ↓ start() RUNNABLE可运行 ↓ 获得CPU时间片 ↓ 等待锁 / sleep / join / IO BLOCKED阻塞 ← 获得锁 → RUNNABLE WAITING等待 ← notify / notifyAll → RUNNABLE TIMED_WAITING超时等待 ← 超时结束 → RUNNABLE ↓ run()执行完毕 TERMINATED终止3.2 状态转换示例ThreadtnewThread(()-{try{Thread.sleep(1000);// TIMED_WAITING}catch(InterruptedExceptione){e.printStackTrace();}});// t的状态NEWt.start();// t的状态RUNNABLE// t执行完毕后// t的状态TERMINATED四、线程同步4.1 为什么需要同步踩坑提醒多线程操作共享数据时不加同步机制会导致数据不一致。我曾经遇到过一个Bug多个线程同时修改库存数量结果出现超卖。这就是典型的竞态条件问题。// 不加同步的例子多个线程同时操作共享数据classCounter{privateintcount0;publicvoidincrement(){count;// 不是原子操作}}// count 实际上是3步操作// 1. 读取count的值// 2. count 1// 3. 写回count// 如果两个线程同时执行count可能出现// 线程A读取count0 → 线程B读取count0// 线程A写入count1 → 线程B写入count1// 结果应该是2实际是1数据不一致4.2 synchronized关键字// 方式1同步代码块classCounter{privateintcount0;privatefinalObjectlocknewObject();publicvoidincrement(){synchronized(lock){// 锁住对象count;}}}// 方式2同步方法classCounter{privateintcount0;publicsynchronizedvoidincrement(){// 锁住thiscount;}}// 方式3同步静态方法classCounter{privatestaticintcount0;publicstaticsynchronizedvoidincrement(){// 锁住Class对象count;}}4.3 Lock接口更灵活经验之谈Lock比synchronized更灵活可以实现公平锁、可中断锁、超时获取锁等。但使用Lock时一定要在finally中释放锁否则一旦出现异常锁就永远释放了其他线程会一直阻塞。importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;classCounter{privateintcount0;privatefinalLocklocknewReentrantLock();publicvoidincrement(){lock.lock();// 加锁try{count;}finally{lock.unlock();// 一定要在finally中释放锁}}}踩坑提醒忘记在finally中unlock是Lock使用的常见错误。相比之下synchronized会自动释放锁更安全一些。4.4 volatile关键字// volatile保证可见性不保证原子性classVolatileDemo{privatevolatilebooleanrunningtrue;publicvoidstop(){runningfalse;// 一个线程修改}publicvoidrun(){while(running){// 另一个线程能立即看到变化// 执行任务}}}// volatile vs synchronized// volatile保证可见性不保证原子性// synchronized保证可见性和原子性五、线程通信5.1 wait和notifyclassSharedResource{privateintcount0;// 生产者publicsynchronizedvoidproduce()throwsInterruptedException{while(count10){wait();// 等待释放锁}count;System.out.println(生产count);notifyAll();// 通知消费者}// 消费者publicsynchronizedvoidconsume()throwsInterruptedException{while(count0){wait();// 等待}count--;System.out.println(消费count);notifyAll();// 通知生产者}}5.2 sleep和wait的区别特性sleepwait所属类ThreadObject释放锁不释放释放使用位置任何地方synchronized块中唤醒方式自动超时notify/notifyAll六、线程池6.1 为什么需要线程池// 不用线程池// 每次执行任务都创建新线程// 创建和销毁线程开销大// 线程数量不可控可能OOM// 用线程池// 复用线程减少创建销毁开销// 控制最大并发数// 提供任务队列和拒绝策略6.2 创建线程池importjava.util.concurrent.*;// 1. 固定大小线程池ExecutorServicepool1Executors.newFixedThreadPool(5);// 5个线程任务队列无界// 2. 缓存线程池ExecutorServicepool2Executors.newCachedThreadPool();// 线程数量不固定按需创建// 3. 单线程池ExecutorServicepool3Executors.newSingleThreadExecutor();// 只有1个线程保证任务按顺序执行// 4. 定时任务线程池ScheduledExecutorServicepool4Executors.newScheduledThreadPool(3);pool4.schedule(()-System.out.println(3秒后执行),3,TimeUnit.SECONDS);pool4.scheduleAtFixedRate(()-System.out.println(定时执行),0,1,TimeUnit.SECONDS);6.3 ThreadPoolExecutor推荐踩坑提醒阿里巴巴Java开发手册明确规定生产环境禁止使用Executors的快捷方法创建线程池因为newFixedThreadPool和newSingleThreadExecutor使用的是无界队列任务堆积会导致OOMnewCachedThreadPool允许无限创建线程也会导致OOM。// 生产环境推荐手动创建线程池不用Executors工厂方法ThreadPoolExecutorpoolnewThreadPoolExecutor(2,// 核心线程数5,// 最大线程数60L,// 空闲线程存活时间TimeUnit.SECONDS,// 时间单位newArrayBlockingQueue(100),// 任务队列有界队列Executors.defaultThreadFactory(),// 线程工厂newThreadPoolExecutor.CallerRunsPolicy()// 拒绝策略);// 提交任务pool.execute(()-System.out.println(任务1));// 无返回值FutureIntegerfuturepool.submit(()-{// 有返回值return42;});Integerresultfuture.get();// 获取结果// 关闭线程池pool.shutdown();// 不接受新任务等待已提交的任务完成6.4 拒绝策略策略说明AbortPolicy抛出RejectedExecutionException默认CallerRunsPolicy由提交任务的线程执行DiscardPolicy直接丢弃任务DiscardOldestPolicy丢弃队列中最老的任务参考资料Oracle官方文档 - ConcurrencyBaeldung - Java Concurrency Tutorial七、面试高频考点考点1start和run的区别// start()启动新线程在子线程中执行run()// run()普通方法调用在当前线程中执行追问多次调用start会怎样答案会抛出IllegalThreadStateException。线程一旦启动就不能再次启动。考点2synchronized和Lock的区别特性synchronizedLock锁释放自动释放手动unlock中断不可中断可以中断公平性非公平可选公平条件绑定一个锁一个条件一个锁多个条件延伸什么情况下用Lock不用synchronized答案需要公平锁、可中断锁、超时获取锁、多个条件变量时用Lock更灵活。考点3线程池的核心参数// corePoolSize核心线程数// maximumPoolSize最大线程数// keepAliveTime空闲线程存活时间// workQueue任务队列// handler拒绝策略追问线程池的执行流程是怎样的答案提交任务→核心线程是否已满否创建核心线程执行任务是加入队列队列已满否加入队列是创建非核心线程非核心线程数达上限否创建非核心线程是执行拒绝策略。八、总结今天我们学习了✅ 进程和线程的概念✅ 创建线程的四种方式✅ 线程的生命周期✅ 线程同步synchronized、Lock、volatile✅ 线程通信wait/notify✅ 线程池的使用重点记忆推荐用Runnable或线程池创建线程start()启动线程run()只是普通调用synchronized保证原子性和可见性volatile只保证可见性生产环境用手动创建的ThreadPoolExecutor JavaSE全面教学系列完结恭喜你学完了JavaSE全面教学系列的全部15篇文章学习路线回顾Week 1Day1-5基础语法 ✅ Week 2Day6-10面向对象 ✅ Week 3Day11-15进阶特性 ✅下一步建议复习本系列所有文章把每篇的代码都自己敲一遍刷LeetCode巩固语法学习JavaWeb/框架Spring Boot等做一个完整的项目互动话题恭喜你完成了JavaSE全面教学系列你学完之后有什么收获或困惑欢迎在评论区分享如果这个系列对你有帮助欢迎点赞、收藏、关注三连支持后续我会更新Java进阶系列关注我不迷路本文为【JavaSE全面教学】系列第15篇完结感谢你的学习