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

【JavaEE】多线程(线程安全问题)

有些代码在单个线程环境下执行正确,如果同样的代码在多个线程下同时执行可能就会出现问题,这个就是线程安全问题(或者称线程不安全问题),简而言之就是:线程安全问题是由于多线程出现的问题,原因是在多线程条件下存在数据共享。 

线程安全问题

1. 观察线程不安全

       下面这个例子是两个线程同时对一个变量进行自增50000次,正常情况下如果我们对一个变量自增两次五万次,结果应该是十万,但下图的结果却是其他数字,这种实际情况与预期不符的现象就是线程不安全。相反如果我们改变一些顺序就可以出现预期的结果了,只是两个线程不再是同时执行。

在这之前需要了解一个知识点,站在cpu的角度看count++,它其实是分成三步的(第一步:load 把数据从内存读到cpu上;第二步:add 把寄存器中的数据加1;第三步:save 把寄存器中的数据保存到内存中)

2. 出现线程不安全的原因

  2.1 操作系统对线程的调度是随机的

什么是调度顺序?每一步的执行顺序。

t1可能先执行load、add、save, 然后再是t2的 load 、add、save;也可能是 t1 可能先执行load、add, 然后是t2的 load 、add、save,最后是 t1 的save等等很多情况

为什么调度顺序不一样产生的结果不一样呢?我们再进行深度剖析。挑选一个同时的调度顺序进行展开叙述:每个线程都有自己的cpu,方块代表内存,椭圆代表cpu;

    以下这个是一个线程一个线程执行的,先进行t1再进行t2的情况,最终的结果是2,结果这个没有问题(这里每个线程我只进行一次自增,剩下的自增结果是类似的)

    接着下面这个调度顺序是两个线程同时进行出现不同的结果,同样是自增一次,最后内存结果确实1,这就是调度顺序引起的线程不安全

  2.2 多个线程修改同一个变量

t1和t2同时对count进行修改自增 也引起了线程不安全,如果是一个一个执行就不存在线程安全问题(看上图)。

  2.3 修改操作不是原子的

也因为自增是分成三步的导致调度顺序不同,产生的结果不同。

  2.4 内存可见性
  2.5 指令重排性

3. 解决线程不安全

想要解决线程安全问题,就需要从以上原因入手。线程调度随机是由系统解决的这个无法改变;同时修改一个变量有时可以通过调换代码顺序进行解决,有些情况下不可以;操作不是原子可以通过加锁实现(给每一个步骤都加锁变成原子),剩下的内存可见性和指令重排序在这个代码中不存在,另外讨论。


加锁(synchronized)

在已经加锁的状态下,另一个线程尝试同样加这个锁,就会产生锁冲突(也叫锁竞争),后面那个线程就会阻塞,直到前面那个线程解锁。

将上面代码进行加锁得到以下代码。

接着进行深度的理解每一步的过程

等待中也就意味着阻塞,然而阻塞也就避免了每个线程的三步进行"串行",于是线程安全问题也就解决掉了。

synchronized不仅可以修饰代码块,还可以修饰实例方法和静态方法,下面是实例方法

public class Demo02 {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.insert();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.insert();}});t1.start();t2.start();t1.join();t2.join();System.out.println("counter.count:"+counter.count);}
}
class Counter{public int count ;//方法1:
//    synchronized public void insert(){ //修饰实例方法
//        count++;
//    }//上面这中写法等价于下面这种,方法二:public void insert(){synchronized(this){count++;}}
}

静态方法

http://www.xdnf.cn/news/1331119.html

相关文章:

  • 中国大学MOOC-C语言第九周指针(上)
  • 数据结构:利用旋转在AVL树中维持平衡(Inserting in AVL with Rotation)
  • 自建开发工具IDE(一)之拖找排版—仙盟创梦IDE
  • RabbitMQ 基础
  • 吱吱企业通讯软件保证内部通讯安全,搭建数字安全体系
  • Windows 中的“计数器”
  • TDengine IDMP 运维指南(数据导入导出)
  • 第三阶段数据-3:数据库脚本生成,备份与还原,分离与附加
  • RabbitMQ:SpringAMQP Topic Exchange(主题交换机)
  • Oracle:配置让插入语句时id自动输入
  • 生产环境MongoDB分片策略优化与故障排查实战经验分享
  • 翻译记忆库(TMX)与机器翻译的结合应用
  • ​​pytest+yaml+allure接口自动化测试框架
  • 计算机视觉(二)------OpenCV图像视频操作进阶:从原理到实战
  • MYSQL-增删查改CRUD
  • 遥感机器学习入门实战教程|Sklearn 案例④ :多分类器对比(SVM / RF / kNN / Logistic...)
  • 【C++】--指针与引用深入解析和对比
  • 2025 | 腾讯混元RLVMR颠覆强化学习:可验证推理奖励引爆AI智能体新范式!
  • 文本智能抽取:如何用NLP从海量文本中“炼“出真金?-告别无效阅读,让AI成为你的“信息炼金师
  • git 生成 Patch 和打 Patch
  • 在完全没有无线网络(Wi-Fi)和移动网络(蜂窝数据)的环境下,使用安卓平板,通过USB数据线(而不是Wi-Fi)来控制电脑(版本2)
  • 汽车ECU实现数据安全存储(机密性保护)的一种方案
  • 网页作品惊艳亮相!这个浪浪山小妖怪网站太治愈了!
  • uni-app跨端开发最后一公里:详解应用上架各大应用商店全流程
  • 云计算学习100天-第26天
  • 《CDN加速的安全隐患与解决办法:如何构建更安全的网络加速体系》
  • 【Ansible】变量、机密、事实
  • Ubuntu-安装Epics Archiver Appliance教程
  • ansible playbook 实战案例roles | 实现基于firewalld添加端口
  • 如何使用matlab将目录下不同的excel表合并成一个表