news 2026/5/26 17:02:57

Java线程安全和同步

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java线程安全和同步

目录

1.场景

2.编写程序

3.解决办法:线程同步

1)synchronized同步代码块

2)synchronized同步方法

3)两个方式的对比

4)写一个锁对象

4.使用场景

使用Lock的场景

使用synchronized的场景


1.场景

小明和小红是一对夫妻,他们有一个共同的账户,有余额十万块钱,有一天他们各自同时取了十万块钱。那谁能成功取到这十万呢?

2.编写程序

1)创建账户类,创建账户对象来代表夫妻的共同账户,提供取钱方法

package ThreadSecurity; public class Account { private Integer money; public Account() {} public Account(Integer money) { this.money = money; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } //取钱方法 public void drawMoney(Integer money) { //看看是谁来取钱 String name = Thread.currentThread().getName(); //验证余额 if (this.money >= money) { //先打印一次,以免程序执行过快反而不出现安全问题 System.out.println(name+"取钱成功,取出了"+money+"元"); this.money -= money ; System.out.println(name + "取钱成功,余额" + this.money + "元"); } else { System.out.println("余额不足"); } } }

2)使用线程类,运行两个线程来模拟取钱过程

package ThreadSecurity; public class MyThread implements Runnable{ private Account account; public MyThread(Account account) { this.account = account; } @Override public void run() { account.drawMoney(100000); } }

3)测试

package ThreadSecurity; /** * 模拟线程同步问题 * 小明和小红同时取同一个账户的钱 */ public class Test { public static void main(String[] args) { //创建账户类 Account account = new Account(100000); //创建小明线程 Thread xm=new Thread(new MyThread(account)); xm.setName("小明"); xm.start(); //创建小红线程 Thread xh=new Thread(new MyThread(account)); xh.setName("小红"); xh.start(); } }

结果:(也有可能是小红取钱成功)

可见,如果两个人都取钱成功,那么银行就会亏损十万元。

那么该怎么解决呢?

3.解决办法:线程同步

1)synchronized同步代码块

作用:把访问共享资源的核心代码块上锁,以此保证线程安全

在核心逻辑取钱方法当中修改:

package ThreadSynchronized01; public class Account { private Integer money; public Account() {} public Account(Integer money) { this.money = money; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } //取钱方法 public void drawMoney(Integer money) { //看看是谁来取钱 String name = Thread.currentThread().getName(); //synchronized代码块:给代码块上锁 //线程通过竞争锁,拿到锁对象(this)之后可以执行代码块,执行完之后释放 synchronized (this) { //验证余额 if (this.money >= money) { //先打印一次,以免程序执行过快反而不出现安全问题 System.out.println(name+"取钱成功,取出了"+money+"元"); this.money -= money ; System.out.println(name + "取钱成功,余额" + this.money + "元"); } else { System.out.println(name+"取钱失败,余额不足"); } } } }

语法

synchronized(锁对象){

//业务逻辑......

}

锁对象可以是其他对象,但是不建议,因为如果是某一常量的话,可能会影响其他无关线程的运行。用this合适,因为这样不会影响其他无关线程的执行,也能起到锁的作用。

对于实例方法,建议使用this作为锁对象

对于静态方法,建议使用字节码(类名.class)作为锁对象

执行结果:(结果不唯一)

2)synchronized同步方法

作用:把访问共享资源的核心方法上锁,以此保证线程安全

修改取钱方法:

package ThreadSynchronized02; public class Account { private Integer money; public Account() {} public Account(Integer money) { this.money = money; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } //取钱方法 //synchronized同步方法,和synchronized代码块大致相同 //synchronized方法的性能比synchronized代码块低,因为synchronized代码块可以让其他线程先处理没有线程安全的代码 //synchronized同步方法维护了一个隐式锁 public synchronized void drawMoney(Integer money) { //看看是谁来取钱 String name = Thread.currentThread().getName(); //验证余额 if (this.money >= money) { //先打印一次,以免程序执行过快反而不出现安全问题 System.out.println(name+"取钱成功,取出了"+money+"元"); this.money -= money ; System.out.println(name + "取钱成功,余额" + this.money + "元"); } else { System.out.println(name+"取钱失败,余额不足"); } } }

结果:(结果不唯一)

3)两个方式的对比

4)写一个锁对象

Lock是Java并发包java.util.concurrent.locks中提供的显式锁机制,相比synchronized内置锁,它提供了更灵活、功能更丰富的锁控制

以ReentrantLock(可重入锁)为例:

package ThreadLock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { //锁对象,加上final防止锁被修改 private final Lock lock = new ReentrantLock(); private Integer money; public Account() {} public Account(Integer money) { this.money = money; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } //取钱方法 public void drawMoney(Integer money) { //看看是谁来取钱 String name = Thread.currentThread().getName(); //ctrl+alt+t选中代码块 try { //上锁 lock.lock(); //验证余额 if (this.money >= money) { //先打印一次,以免程序执行过快反而不出现安全问题 System.out.println(name+"取钱成功,取出了"+money+"元"); this.money -= money ; System.out.println(name + "取钱成功,余额" + this.money + "元"); } else { System.out.println("余额不足"); } } catch (Exception e) { throw new RuntimeException(e); }finally { //unlock必须执行,如果前面发生异常,也要解锁,所以要在finally里面 lock.unlock(); } } }

结果(不唯一):

4.使用场景

使用Lock的场景

需要高级功能:可中断、超时、尝试锁

需要多个条件变量:复杂的线程协调

读写分离场景:读多写少

需要公平性控制:按顺序获取锁

性能关键场景:高竞争下的性能优化

使用synchronized的场景

简单同步需求:基本的互斥访问

代码简洁性:减少代码复杂度

自动管理:避免忘记释放锁

内建优化:JVM对synchronized有深度优化

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/25 19:31:55

为什么90%的智能工厂失败?:忽视Agent协作的三大致命误区

第一章:智能工厂中工业机器人Agent协作的演进与挑战在智能制造快速发展的背景下,工业机器人不再孤立执行预设任务,而是作为具备感知、决策与通信能力的智能Agent,在动态环境中协同完成复杂生产流程。这种从“自动化”向“自主化”…

作者头像 李华
网站建设 2026/5/26 16:24:00

【家庭自动化革命】:如何用Agent实现跨品牌设备无缝联动?

第一章:家庭自动化革命的现状与挑战家庭自动化已从科幻概念演变为日常生活的一部分。智能灯光、温控系统、安防监控和语音助手等设备正快速普及,推动居住环境向智能化、节能化和个性化方向发展。然而,技术进步的背后仍面临诸多现实挑战。设备…

作者头像 李华
网站建设 2026/5/26 0:53:36

碰碰卡系统源码开发搭建技术分享

碰碰卡源码 /碰一碰发视频系统源码/NFC碰碰卡源码/碰一碰智能营销系统源码开发搭建 碰碰卡系统开发概述 碰碰卡系统通常指一种基于卡片交互的游戏或营销系统,涉及前端界面、后端逻辑及数据库设计。开发需结合具体需求,如游戏规则、用户管理、数据统计等…

作者头像 李华
网站建设 2026/5/26 16:21:26

错题识别不准?试试这7种提升自动批改效果的强化策略

第一章:错题识别不准?自动批改的挑战与破局思路在教育科技快速发展的背景下,自动批改系统已成为提升教学效率的重要工具。然而,实际应用中“错题识别不准”成为制约其推广的核心痛点。传统规则匹配方法难以应对学生作答的多样性&a…

作者头像 李华
网站建设 2026/5/26 0:53:33

22、资源泄漏与线程同步技术详解

资源泄漏与线程同步技术详解 1. 资源泄漏问题 资源泄漏是软件不稳定的重要原因之一,常见的资源泄漏类型包括句柄泄漏和内存泄漏。 例如有如下代码: SomeFunc(); delete[] ptr; }如果 SomeFunc 函数抛出异常,且该异常未被捕获,那么函数将发生内存泄漏,具体会泄漏 25…

作者头像 李华
网站建设 2026/5/26 5:38:22

26、《Windows 调试:从 32 位到 64 位及事后调试全解析》

《Windows 调试:从 32 位到 64 位及事后调试全解析》 1. 32 位与 64 位系统交互及调试扩展 在 32 位与 64 位系统交互方面,当注册一个 32 位 DCOM 服务器应用程序后,其注册信息会自动显示在 64 位注册表视图中。64 位客户端可以实例化并使用运行在 WOW64 仿真环境中的 32 …

作者头像 李华