当前位置: 首页 > news >正文

14-Java多线程入门:synchronized、volatile与线程通信

Java多线程入门:synchronized、volatile与线程通信

开篇:面试被问多线程,你总是答不到点?

多线程是Java面试的"重灾区"。几乎每场面试都会被问到:synchronized原理、volatile作用、线程通信、生产者消费者模型。

很多人觉得多线程难,是因为知识点太散。本文把多线程入门的核心内容串成一条线,帮你建立完整知识框架。

一、为什么要用多线程

程序运行时,CPU是最宝贵的资源。单线程程序在遇到IO操作(读写文件、网络请求)时,CPU只能空等。

多线程可以让CPU在等待IO时,去执行其他任务,提高资源利用率。

单线程:任务A执行 → IO等待 → 继续任务A 多线程:任务A执行 → IO等待 → 切换到任务B执行

【面试高频】多线程能提高吞吐量,但不一定缩短单个任务的执行时间。

二、线程创建的三种方式

2.1 继承 Thread 类

classMyThreadextendsThread{@Overridepublicvoidrun(){System.out.println("线程执行:"+Thread.currentThread().getName());}}MyThreadt1=newMyThread();t1.start();

2.2 实现 Runnable 接口(推荐)

classMyRunnableimplementsRunnable{@Overridepublicvoidrun(){System.out.println("线程执行:"+Thread.currentThread().getName());}}Threadt2=newThread(newMyRunnable());t2.start();

2.3 实现 Callable 接口(有返回值)

classMyCallableimplementsCallable<String>{@OverridepublicStringcall()throwsException{return"任务结果";}}FutureTask<String>futureTask=newFutureTask<>(newMyCallable());newThread(futureTask).start();Stringresult=futureTask.get();// 阻塞获取结果

【面试高频】推荐用RunnableCallable,因为Java是单继承,继承Thread会限制扩展性。

三、线程的6种状态

NEW(新建) → RUNNABLE(可运行) → RUNNING(运行中) → BLOCKED(阻塞) → WAITING(等待) → TIMED_WAITING(超时等待) → TERMINATED(终止)
状态说明
NEW线程刚创建,未调用start
RUNNABLE等待CPU调度
BLOCKED等待锁
WAITING无限等待,需要被唤醒
TIMED_WAITING限时等待
TERMINATED执行完毕

【面试陷阱】线程调用start()后不是立刻执行,而是进入RUNNABLE状态等待CPU调度。

四、线程安全问题

4.1 问题的根源

多个线程同时读写同一个共享变量,就可能出现数据不一致。例如:

publicclassCounter{privateintcount=0;publicvoidincrement(){count++;// 不是原子操作}}

两个线程各执行100万次,结果通常小于200万。

4.2 线程安全三条件

  1. 多线程环境
  2. 存在共享资源
  3. 对共享资源有写操作

只要破坏其中一个条件,就能避免线程安全问题。

五、synchronized关键字

5.1 三种用法

// 1. 同步代码块synchronized(obj){// 临界区}// 2. 同步实例方法:锁对象是 thispublicsynchronizedvoidmethod(){// 临界区}// 3. 同步静态方法:锁对象是 Class 对象publicstaticsynchronizedvoidmethod(){// 临界区}

5.2 锁对象必须相同

多个线程只有竞争同一把锁,synchronized才有意义。

// 错误:每次new新对象当锁synchronized(newObject()){count++;}

5.3 一个完整示例

publicclassSafeCounter{privateintcount=0;publicsynchronizedvoidincrement(){count++;}publicsynchronizedintgetCount(){returncount;}}

【面试高频】synchronized在JDK6之后做了大量优化,包括偏向锁、轻量级锁、重量级锁,性能已经很好。

六、volatile关键字

6.1 volatile的作用

volatile有两个作用:

  1. 保证可见性:一个线程修改了变量,其他线程能立即看到。
  2. 禁止指令重排序:防止编译器和CPU优化执行顺序。

6.2 volatile不能保证原子性

publicclassVolatileDemo{privatevolatileintcount=0;publicvoidincrement(){count++;// 仍然不是线程安全的}}

count++包含读取、修改、写回三个步骤。volatile只能保证最后一步写回后被其他线程看到,但不能保证中间不被打断。

【面试陷阱】volatile适用于状态标志位,不适用于计数器。

6.3 volatile正确用法

publicclassFlagDemo{privatevolatilebooleanrunning=true;publicvoidstop(){running=false;}publicvoiddoWork(){while(running){// 执行任务}}}

七、线程通信

7.1 wait / notify 基础

wait()让当前线程等待并释放锁。notify()唤醒一个等待的线程。notifyAll()唤醒所有等待的线程。

这三个方法必须在synchronized代码块中调用,并且调用对象是锁对象。

7.2 经典案例:生产者消费者

一个线程生产数据,一个线程消费数据,通过wait/notify协调。

publicclassBuffer{privateintdata;privatebooleanempty=true;publicsynchronizedvoidproduce(intvalue)throwsInterruptedException{while(!empty){wait();// 缓冲区有数据,等待消费}data=value;empty=false;notifyAll();// 唤醒消费者}publicsynchronizedintconsume()throwsInterruptedException{while(empty){wait();// 缓冲区为空,等待生产}empty=true;notifyAll();// 唤醒生产者returndata;}}

【面试高频】为什么用while而不是if?因为被唤醒后条件可能又不满足了,while会再次判断。

八、与Spring框架的连接

Spring的@Async注解底层就是线程池:

@ServicepublicclassOrderService{@Async("taskExecutor")publicvoidsendSms(Stringphone){// 异步发送短信}}

配置线程池:

@Bean("taskExecutor")publicExecutortaskExecutor(){ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(20);executor.setQueueCapacity(100);executor.initialize();returnexecutor;}

【面试高频】实际开发中很少直接new Thread(),而是用线程池管理线程。

九、开发踩坑实录

坑 1:调用 run() 而不是 start()

Threadt=newThread(newMyRunnable());t.run();// 普通方法调用,没有启动新线程

后果:代码在当前线程同步执行,没有多线程效果。

正确做法:调用t.start()

坑 2:synchronized锁错对象

publicsynchronizedvoidmethod(){// 锁是 thiscount++;}

如果两个线程传入的是不同实例,synchronized就锁不住了。

正确做法:共享变量加static,或者锁同一个全局对象。

坑 3:在锁里做耗时操作

synchronized(lock){Thread.sleep(10000);// 持有锁10秒}

后果:其他线程一直阻塞,性能极差。

正确做法:锁里只放必要操作,IO和耗时计算放到锁外。

坑 4:wait没有放在循环里

synchronized(lock){if(condition){lock.wait();}}

后果:被虚假唤醒后,条件可能又不满足了,程序会出错。

正确做法:用while循环判断条件。

十、面试速记卡

10.1 核心概念速查

概念一句话解释
线程CPU调度的基本单位
synchronized互斥锁,保证同一时刻只有一个线程执行
volatile保证可见性、禁止重排序,不保证原子性
wait释放锁并等待
notify唤醒一个等待线程
notifyAll唤醒所有等待线程
线程池管理和复用线程,减少创建销毁开销

10.2 高频面试题

  1. 线程创建的三种方式是什么?推荐哪种?
  2. start()run()有什么区别?
  3. synchronized的锁对象分别是什么?
  4. volatile能保证线程安全吗?为什么?
  5. sleep()wait()有什么区别?
  6. 为什么wait()要放在循环里?
  7. 什么是虚假唤醒?
  8. 生产者消费者模型怎么实现?

10.3 口诀总结

线程创建三方式,Runable更推荐 start启动才有效,run只是普通调 synchronized上把锁,对象相同才有效 volatile可见不原子,状态标志最适合 wait会释放锁,notify要唤醒 while判断条件稳,虚假唤醒也不怕

十一、小结

多线程入门抓住三个重点:线程如何创建线程安全问题如何解决线程之间如何通信

synchronized解决互斥,volatile解决可见性,wait/notify解决协作。掌握这三个,面试中的基础多线程问题基本都能应对。

实际开发中尽量少直接操作线程,优先使用线程池和框架提供的异步能力。

http://www.jsqmd.com/news/1199235/

相关文章:

  • 滨州黄金回收6家正规门店,实时金价透明结算 - 余生黄金回收
  • Seaborn 并不是一个“科学计算工具集”,而是一个基于 matplotlib 的**高级数据可视化库**
  • Unity升级后Android构建错误:迁移res资源到AAR的完整指南
  • C++实战:从零构建校友管理系统,掌握三层架构与RESTful API开发
  • 环保领域“固废处理”高价值专利案例:垃圾焚烧炉SNCR脱硝系统动态建模方法
  • 游戏内存修改技术详解:从Cheat Engine原理到《财富磨坊》实战
  • Three.js 点击第三人称移动教程
  • 鸿蒙 构建系统:多模块管理
  • 2026 南宁邕宁区老工艺黄金回收 易奢福老式金饰专业评估价值 - ys韩
  • Witty-Insight技能分析:AI Agent工具使用统计与性能评估
  • C++ string容器深度解析:从RAII到移动语义的实战指南
  • sshd配置实战:从基础安全到高级调优
  • C++时延优化实战:从内存管理到无锁编程的确定性性能提升
  • 告别性能瓶颈:如何用开源工具永久掌控Windows安全防护
  • 大模型RAG架构下的信源选择机制:从检索到引用的六阶段解析
  • 嵌入式机器学习 - 学习笔记1.0.1 - 为什么机器学习开始跑进微控制器?
  • [简化版 GAMES 104] 现代游戏引擎 01:从代码到世界:现代游戏引擎的恢弘史诗
  • 从7z到ZIP/RAR:为何数据恢复性应成为压缩格式选型的关键考量
  • Java AI编码工具实战评测:脚手架生成能力深度对比
  • robots.txt 完全指南:从入门到精通
  • CVE-2020-8840:从JNDI注入看Jackson-databind黑名单攻防演进
  • 2026靠谱铜覆扁钢厂家怎么选?认准资质齐全的源头大厂 - 商业新知
  • 2026深圳劳力士回收靠谱商家盘点:水贝S+级机构724小时上门,闲置名表快速变现 - 名奢变现站
  • 中小企业如何真正落地 AI?4 个实践经验,少走很多弯路
  • 从零到一:STM32工程创建中启动文件的精准匹配与配置实战
  • 新E选的烤火罩会不会显得特别笨重、松松垮垮的?
  • pdf压缩免费版哪个好?5款工具横评实测,压缩效果一目了然 - 博客万
  • 线上投票评选怎么做,云众评选 3‑5 分钟快速搭建投票活动 - 微信投票小程序
  • 【架构剖析】图书管理系统——核心模块与数据流转设计详解
  • 换脸保特征:用于ITS中行人隐私的双用途管道